Skip to content

Commit

Permalink
Redesign to work across navigations
Browse files Browse the repository at this point in the history
  • Loading branch information
srcrip committed Dec 18, 2024
1 parent cef23f7 commit ed486ab
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 25 deletions.
4 changes: 4 additions & 0 deletions assets/js/live_toast/live_toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ export function createLiveToastHook(duration = 6000, maxItems = 3) {
}
}

window.addEventListener('phx:clear-flash', e => {
this.pushEvent('lv:clear-flash', { key: e.detail.key })
})

window.addEventListener('flash-leave', async event => {
if (event.target === this.el) {
// animate this flash sliding out
Expand Down
53 changes: 34 additions & 19 deletions lib/live_toast.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ defmodule LiveToast do
:component,
:duration,
:container_id,
:uuid
:uuid,
:sync
]

@typedoc "Instance of a toast message. Mainly used internally."
Expand All @@ -31,7 +32,8 @@ defmodule LiveToast do
component: component_fn() | nil,
duration: non_neg_integer() | nil,
container_id: binary() | nil,
uuid: Ecto.UUID.t() | nil
uuid: Ecto.UUID.t() | nil,
sync: boolean() | nil
}

@typedoc "`Phoenix.Component` that renders a part of the toast message."
Expand All @@ -46,6 +48,25 @@ defmodule LiveToast do
| {:duration, non_neg_integer() | nil}
| {:container_id, binary() | nil}
| {:uuid, Ecto.UUID.t() | nil}
| {:sync, boolean() | nil}

defp make_toast(kind, msg, options \\ []) do
container_id = options[:container_id] || "toast-group"
uuid = options[:uuid] || Ecto.UUID.generate()

%LiveToast{
kind: kind,
msg: msg,
title: options[:title],
icon: options[:icon],
action: options[:action],
component: options[:component],
duration: options[:duration],
container_id: container_id,
uuid: uuid,
sync: options[:sync] || false
}
end

@doc """
Send a new toast message to the LiveToast component.
Expand All @@ -61,24 +82,11 @@ defmodule LiveToast do
"""
@spec send_toast(atom(), binary(), [option()]) :: Ecto.UUID.t()
def send_toast(kind, msg, options \\ []) do
container_id = options[:container_id] || "toast-group"
uuid = options[:uuid] || Ecto.UUID.generate()

toast = %LiveToast{
kind: kind,
msg: msg,
title: options[:title],
icon: options[:icon],
action: options[:action],
component: options[:component],
duration: options[:duration],
container_id: container_id,
uuid: uuid
}
toast = make_toast(kind, msg, options)

LiveView.send_update(LiveToast.LiveComponent, id: container_id, toasts: [toast])
LiveView.send_update(LiveToast.LiveComponent, id: toast.container_id, toasts: [toast])

uuid
toast.uuid
end

@doc """
Expand Down Expand Up @@ -107,9 +115,13 @@ defmodule LiveToast do

@spec put_toast(LiveView.Socket.t(), atom(), binary(), [option()]) :: LiveView.Socket.t()
def put_toast(%LiveView.Socket{} = socket, kind, msg, options) do
send_toast(kind, msg, options)
options = Keyword.put(options, :sync, true)

toast = make_toast(kind, msg, options)

socket
|> Phoenix.LiveView.put_flash(kind, msg)
|> Phoenix.Component.assign(toasts_sync: [toast])
end

@doc """
Expand Down Expand Up @@ -213,6 +225,8 @@ defmodule LiveToast do
doc: "function to override the toast classes"
)

attr :toasts_sync, :list, default: nil

@doc """
Renders a group of toasts and flashes.
Expand All @@ -224,6 +238,7 @@ defmodule LiveToast do
:if={@connected}
id={@id}
module={LiveToast.LiveComponent}
toasts_sync={@toasts_sync}
corner={@corner}
toast_class_fn={@toast_class_fn}
group_class_fn={@group_class_fn}
Expand Down
54 changes: 48 additions & 6 deletions lib/live_toast/live_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,56 @@ defmodule LiveToast.LiveComponent do

@impl Phoenix.LiveComponent
def update(assigns, socket) do
{toasts, assigns} = Map.pop(assigns, :toasts)
toasts = toasts || []
# todo: make sure this works when doing multiple toasts at once. even tho thats unlikely.
# handling of synchronous toasts when calling put_toast
# basically, we need to read assigns["toasts_sync"], to see if there was a new toast popped on from put_toast.
# If there was, we need to look for a corresponding flash message (with the same kind and message) and remove it.
sync_toasts = Map.get(assigns, :toasts_sync, [])

sync_toast =
if sync_toasts && sync_toasts != [] do
List.first(sync_toasts)
else
%{}
end

flash_map = assigns[:f]

sync_toast_kind = Map.get(sync_toast, :kind, nil)

sync_toast_kind =
if is_atom(sync_toast_kind) do
Atom.to_string(sync_toast_kind)
end

f =
flash_map[sync_toast_kind]

socket =
socket
|> assign(assigns)
|> stream(:toasts, toasts)
|> assign(:toast_count, socket.assigns.toast_count + length(toasts))
if f && f == Map.get(sync_toast, :msg) do
{toasts, assigns} = Map.pop(assigns, :toasts)
toasts = toasts || []
toasts = [sync_toast | toasts]

new_f = put_in(assigns[:f][sync_toast_kind], nil)
assigns = Map.put(assigns, :f, new_f)

socket
|> assign(assigns)
|> stream(:toasts, toasts)
|> assign(:toast_count, socket.assigns.toast_count + length(toasts))
# instead of clearing flash here, we jsut send a message to the frontend to do it.
# The advantage is this makes it work properly even across a navigation.
|> push_event("clear-flash", %{key: sync_toast.kind})
else
{toasts, assigns} = Map.pop(assigns, :toasts)
toasts = toasts || []

socket
|> assign(assigns)
|> stream(:toasts, toasts)
|> assign(:toast_count, socket.assigns.toast_count + length(toasts))
end

{:ok, socket}
end
Expand Down

0 comments on commit ed486ab

Please sign in to comment.