Packages
livebook
0.3.1
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook_web/live/session_live/input_cell_settings_component.ex
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"""
<div class="p-6 pb-4 flex flex-col space-y-8">
<h3 class="text-2xl font-semibold text-gray-800">
Cell settings
</h3>
<form
phx-submit="save"
phx-change="validate"
phx-target={@myself}
spellcheck="false"
autocomplete="off">
<div class="flex flex-col space-y-6">
<div>
<div class="input-label">Type</div>
<.select name="attrs[type]" selected={@attrs.type} options={input_types()} />
</div>
<div>
<div class="input-label">Name</div>
<input type="text" class="input" name="attrs[name]" value={@attrs.name} autofocus />
</div>
<.extra_fields type={@attrs.type} props={@attrs.props} myself={@myself} />
</div>
<div class="mt-8 flex justify-end space-x-2">
<%= live_patch "Cancel", to: @return_to, class: "button button-outlined-gray" %>
<button class="button button-blue" type="submit" disabled={not @valid}>
Save
</button>
</div>
</form>
</div>
"""
end
defp extra_fields(%{type: :range} = assigns) do
~H"""
<div class="flex space-x-4">
<div class="flex-grow">
<div class="input-label">Min</div>
<input type="number" class="input" name="attrs[props][min]" value={@props.min} />
</div>
<div class="flex-grow">
<div class="input-label">Max</div>
<input type="number" class="input" name="attrs[props][max]" value={@props.max} />
</div>
<div class="flex-grow">
<div class="input-label">Step</div>
<input type="number" class="input" name="attrs[props][step]" value={@props.step} />
</div>
</div>
"""
end
defp extra_fields(%{type: :select} = assigns) do
~H"""
<div class="flex flex-col">
<div class="input-label mb-0">Options</div>
<div class="my-2 flex flex-col space-y-2">
<%= for {option, idx} <- Enum.with_index(@props.options) do %>
<div class="flex items-center space-x-2">
<input
type="text"
class="input"
name="attrs[props][options][]"
value={option} />
<button
class="button button-gray button-square-icon"
type="button"
tabindex="-1"
phx-target={@myself}
phx-click="select_options_action"
phx-value-action="delete"
phx-value-index={idx}
disabled={length(@props.options) == 1}>
<.remix_icon icon="delete-bin-6-line" />
</button>
</div>
<% end %>
</div>
<div>
<button
class="button button-outlined-gray"
type="button"
phx-target={@myself}
phx-click="select_options_action"
phx-value-action="add">
Add
</button>
</div>
</div>
"""
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