mirror of
https://github.com/livebook-dev/livebook.git
synced 2024-11-15 12:25:31 +08:00
197 lines
5.5 KiB
Elixir
197 lines
5.5 KiB
Elixir
defmodule LivebookWeb.Confirm do
|
|
use Phoenix.Component
|
|
|
|
import LivebookWeb.CoreComponents
|
|
import LivebookWeb.FormComponents
|
|
import Phoenix.LiveView
|
|
|
|
alias Phoenix.LiveView.JS
|
|
|
|
@doc """
|
|
Shows a confirmation modal.
|
|
|
|
On confirmation runs `on_confirm`. The function receives a socket
|
|
and should return the socket. Note that this socket always comes from
|
|
the root LV level, keep this in mind when using in a component and
|
|
dealing with assigns.
|
|
|
|
Make sure to render `confirm_root/1` in the layout.
|
|
|
|
## Options
|
|
|
|
* `:title` - title of the confirmation modal. Defaults to `"Are you sure?"`
|
|
|
|
* `:description` - content of the confirmation modal. Required
|
|
|
|
* `:confirm_text` - text of the confirm button. Defaults to `"Yes"`
|
|
|
|
* `:confirm_icon` - icon in the confirm button. Optional
|
|
|
|
* `:danger` - whether the action is destructive or regular. Defaults to `true`
|
|
|
|
* `:opt_out_id` - enables the "Don't show this message again"
|
|
checkbox. Once checked by the user, the confirmation with this
|
|
id is never shown again. Optional
|
|
|
|
* `:options` - a list of togglable options to present alongside the
|
|
confirmation message. Each option must be a map:
|
|
|
|
```
|
|
%{
|
|
name: String.t(),
|
|
label: String.t(),
|
|
default: boolean(),
|
|
disabled: boolean()
|
|
}
|
|
```
|
|
|
|
The option values are passed to the `on_confirm` function as the
|
|
second argument
|
|
|
|
"""
|
|
def confirm(socket, on_confirm, opts) do
|
|
opts =
|
|
Keyword.validate!(
|
|
opts,
|
|
title: "Are you sure?",
|
|
description: nil,
|
|
confirm_text: "Yes",
|
|
confirm_icon: nil,
|
|
danger: true,
|
|
opt_out_id: nil,
|
|
options: []
|
|
)
|
|
|
|
send(self(), {:confirm, on_confirm, opts})
|
|
|
|
socket
|
|
end
|
|
|
|
@doc """
|
|
Renders the confirmation modal for `confirm/3`.
|
|
"""
|
|
|
|
attr :confirm_state, :map, required: true
|
|
|
|
def confirm_root(assigns) do
|
|
~H"""
|
|
<.confirm_modal :if={@confirm_state} id={"confirm-#{@confirm_state.id}"} {@confirm_state.attrs} />
|
|
"""
|
|
end
|
|
|
|
defp confirm_modal(assigns) do
|
|
~H"""
|
|
<.modal id={@id} width={:medium} show={true}>
|
|
<form
|
|
id={"#{@id}-confirm-content"}
|
|
class="p-6 flex flex-col"
|
|
phx-submit={JS.push("confirm") |> hide_modal(@id)}
|
|
data-el-confirm-form
|
|
>
|
|
<h3 class="text-2xl font-semibold text-gray-800">
|
|
<%= @title %>
|
|
</h3>
|
|
<p class="mt-8 text-gray-700">
|
|
<%= @description %>
|
|
</p>
|
|
<div :if={@options != []} class="mt-8">
|
|
<h3 class="mb-2 text-lg font-semibold text-gray-800">
|
|
Options
|
|
</h3>
|
|
<div class="flex flex-col gap-2">
|
|
<.switch_field
|
|
:for={option <- @options}
|
|
name={"options[#{option.name}]"}
|
|
label={option.label}
|
|
value={option.default}
|
|
disabled={option.disabled}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div :if={@opt_out_id} class="mt-6">
|
|
<.checkbox_field
|
|
name="opt_out_id"
|
|
value={nil}
|
|
checked_value={@opt_out_id}
|
|
unchecked_value={nil}
|
|
label="Don't show this message again"
|
|
small
|
|
/>
|
|
</div>
|
|
<div class="mt-8 flex justify-end">
|
|
<div class={["flex gap-2", @danger && "flex-row-reverse"]}>
|
|
<.button color="gray" outlined type="button" phx-click={hide_modal(@id)}>
|
|
Cancel
|
|
</.button>
|
|
<.button color={if(@danger, do: "red", else: "blue")} type="submit">
|
|
<.remix_icon :if={@confirm_icon} icon={@confirm_icon} />
|
|
<span><%= @confirm_text %></span>
|
|
</.button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</.modal>
|
|
"""
|
|
end
|
|
|
|
def on_mount(:default, _params, _session, socket) do
|
|
connect_params = get_connect_params(socket) || %{}
|
|
|
|
# Opting out is a per-user preference, so we store it on the client
|
|
# and send in the connect params
|
|
confirm_opt_out_ids = connect_params["confirm_opt_out_ids"] || []
|
|
|
|
socket =
|
|
socket
|
|
|> assign(confirm_state: nil, confirm_opt_out_ids: MapSet.new(confirm_opt_out_ids))
|
|
|> attach_hook(:confirm, :handle_event, &handle_event/3)
|
|
|> attach_hook(:confirm, :handle_info, &handle_info/2)
|
|
|
|
{:cont, socket}
|
|
end
|
|
|
|
defp handle_event("confirm", params, socket) do
|
|
socket =
|
|
if opt_out_id = params["opt_out_id"] do
|
|
socket
|
|
|> update(:confirm_opt_out_ids, &MapSet.put(&1, opt_out_id))
|
|
|> push_event("add_confirm_opt_out_id", %{opt_out_id: opt_out_id})
|
|
else
|
|
socket
|
|
end
|
|
|
|
options =
|
|
for {name, value} <- params["options"] || [], into: %{}, do: {name, value == "true"}
|
|
|
|
on_confirm = socket.assigns.confirm_state.on_confirm
|
|
|
|
socket =
|
|
cond do
|
|
is_function(on_confirm, 1) -> on_confirm.(socket)
|
|
is_function(on_confirm, 2) -> on_confirm.(socket, options)
|
|
end
|
|
|
|
{:halt, socket}
|
|
end
|
|
|
|
defp handle_event(_event, _params, socket), do: {:cont, socket}
|
|
|
|
defp handle_info({:confirm, on_confirm, opts}, socket) do
|
|
socket =
|
|
if opts[:opt_out_id] && opts[:opt_out_id] in socket.assigns.confirm_opt_out_ids do
|
|
on_confirm.(socket)
|
|
else
|
|
assign(socket,
|
|
confirm_state: %{
|
|
id: Livebook.Utils.random_short_id(),
|
|
on_confirm: on_confirm,
|
|
attrs: opts
|
|
}
|
|
)
|
|
end
|
|
|
|
{:cont, socket}
|
|
end
|
|
|
|
defp handle_info(_message, socket), do: {:cont, socket}
|
|
end
|