From 6358b0bb3baeefca0d6431e9a579bbc3404b0a2e Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Mon, 8 Nov 2021 13:46:23 -0500 Subject: [PATCH] Fix changeset handling by recycling --- assets/css/app.css | 8 +- assets/js/app.js | 8 +- lib/live_beats/media_library.ex | 12 ++- ...{song_entry.ex => song_entry_component.ex} | 12 ++- .../live/song_live/upload_form_component.ex | 52 ++++++---- .../song_live/upload_form_component.html.heex | 96 +++++++++---------- lib/live_beats_web/views/error_helpers.ex | 33 +++++-- 7 files changed, 130 insertions(+), 91 deletions(-) rename lib/live_beats_web/live/song_live/{song_entry.ex => song_entry_component.ex} (81%) diff --git a/assets/css/app.css b/assets/css/app.css index a7209a8..8eb942b 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -11,9 +11,7 @@ .fade-out-scale { animation: 0.2s ease-out 0s normal forwards 1 fade-out-scale-keys; } -.slide-in-right { - animation: slide-in-right-keys 0.2s forwards; -} + .fade-in { animation: 0.2s ease-out 0s normal forwards 1 fade-in-keys; } @@ -75,9 +73,7 @@ display: none; } .invalid-feedback { - color: #a94442; - display: block; - margin: -1rem 0 2rem; + display: inline-block; } /* LiveView specific classes for your customization */ diff --git a/assets/js/app.js b/assets/js/app.js index 4b098f3..2185384 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -27,9 +27,11 @@ Hooks.AudioPlayer = { this.duration = this.el.querySelector("#player-duration") this.progress = this.el.querySelector("#player-progress") let enableAudio = () => { - document.removeEventListener("click", enableAudio) - this.player.play().catch(error => null) - this.player.pause() + if(this.player.src){ + document.removeEventListener("click", enableAudio) + this.player.play().catch(error => null) + this.player.pause() + } } document.addEventListener("click", enableAudio) this.el.addEventListener("js:listen_now", () => this.play({sync: true})) diff --git a/lib/live_beats/media_library.ex b/lib/live_beats/media_library.ex index 8cfbf8e..6c00368 100644 --- a/lib/live_beats/media_library.ex +++ b/lib/live_beats/media_library.ex @@ -187,7 +187,15 @@ defmodule LiveBeats.MediaLibrary do Repo.delete(song) end - def change_song(%Song{} = song, attrs \\ %{}) do - Song.changeset(song, attrs) + def change_song(song_or_changeset, attrs \\ %{}) do + song_or_changeset + |> recycle_changeset() + |> Song.changeset(attrs) end + + defp recycle_changeset(%Ecto.Changeset{} = changeset) do + Map.merge(changeset, %{action: nil, errors: [], valid?: true}) + end + + defp recycle_changeset(%{} = other), do: other end diff --git a/lib/live_beats_web/live/song_live/song_entry.ex b/lib/live_beats_web/live/song_live/song_entry_component.ex similarity index 81% rename from lib/live_beats_web/live/song_live/song_entry.ex rename to lib/live_beats_web/live/song_live/song_entry_component.ex index a737f89..9dd5ce7 100644 --- a/lib/live_beats_web/live/song_live/song_entry.ex +++ b/lib/live_beats_web/live/song_live/song_entry_component.ex @@ -1,8 +1,12 @@ -defmodule LiveBeatsWeb.SongLive.SongEntry do +defmodule LiveBeatsWeb.SongLive.SongEntryComponent do use LiveBeatsWeb, :live_component alias LiveBeats.MP3Stat + def send_progress(%Phoenix.LiveView.UploadEntry{} = entry) do + send_update(__MODULE__, id: entry.ref, progress: entry.progress) + end + def render(assigns) do ~H"""
@@ -19,13 +23,15 @@ defmodule LiveBeatsWeb.SongLive.SongEntry do - <%= error_tag(@errors, :title, "songs[#{@ref}][title]") %>
- <%= error_tag(@errors, :artist, "songs[#{@ref}][artist]") %> +
+
+ <.error input_name={"songs[#{@ref}][title]"} field={:title} errors={@errors}/> + <.error input_name={"songs[#{@ref}][artist]"} field={:artist} errors={@errors}/>
diff --git a/lib/live_beats_web/live/song_live/upload_form_component.ex b/lib/live_beats_web/live/song_live/upload_form_component.ex index 442da3c..4e10097 100644 --- a/lib/live_beats_web/live/song_live/upload_form_component.ex +++ b/lib/live_beats_web/live/song_live/upload_form_component.ex @@ -2,7 +2,7 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do use LiveBeatsWeb, :live_component alias LiveBeats.{MediaLibrary, MP3Stat} - alias LiveBeatsWeb.SongLive.SongEntry + alias LiveBeatsWeb.SongLive.SongEntryComponent @max_songs 10 @@ -12,8 +12,8 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do {:ok, %MP3Stat{} = stat} -> {:ok, put_stats(socket, entry_ref, stat)} - _ -> - {:ok, cancel_upload(socket, :mp3, entry_ref)} + {:error, _} -> + {:ok, cancel_changeset_upload(socket, entry_ref, :not_accepted)} end end @@ -54,7 +54,7 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do |> push_redirect(to: Routes.song_index_path(socket, :index))} {:error, _reason} -> - {:noreply, put_flash(socket, :error, "There were problems uploading your songs")} + {:noreply, socket} end end @@ -69,7 +69,6 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do new_changeset = acc |> get_changeset(ref) - |> Ecto.Changeset.apply_changes() |> MediaLibrary.change_song(song_params) |> Map.put(:action, action) @@ -103,8 +102,12 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do update(socket, :changesets, &Map.put(&1, entry_ref, changeset)) end + defp drop_changeset(socket, entry_ref) do + update(socket, :changesets, &Map.delete(&1, entry_ref)) + end + defp handle_progress(:mp3, entry, socket) do - send_update(SongEntry, id: entry.ref, progress: entry.progress) + SongEntryComponent.send_progress(entry) if entry.done? do async_calculate_duration(socket, entry) @@ -144,21 +147,32 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do defp drop_invalid_uploads(socket) do %{uploads: uploads} = socket.assigns - {new_socket, error_messages, _index} = - Enum.reduce(uploads.mp3.entries, {socket, [], 0}, fn entry, {socket, msgs, i} -> - if i >= @max_songs do - {cancel_upload(socket, :mp3, entry.ref), [{entry.client_name, :dropped} | msgs], i + 1} - else - case upload_errors(uploads.mp3, entry) do - [first | _] -> - {cancel_upload(socket, :mp3, entry.ref), [{entry.client_name, first} | msgs], i + 1} + Enum.reduce(Enum.with_index(uploads.mp3.entries), socket, fn {entry, i}, socket -> + if i >= @max_songs do + cancel_changeset_upload(socket, entry.ref, :dropped) + else + case upload_errors(uploads.mp3, entry) do + [first | _] -> + cancel_changeset_upload(socket, entry.ref, first) - [] -> - {socket, msgs, i + 1} - end + [] -> + socket end - end) + end + end) + end - assign(new_socket, error_messages: error_messages) + defp cancel_changeset_upload(socket, entry_ref, reason) do + entry = get_entry!(socket, entry_ref) + + socket + |> cancel_upload(:mp3, entry.ref) + |> drop_changeset(entry.ref) + |> update(:error_messages, &(&1 ++ [{entry.client_name, reason}])) + end + + defp get_entry!(socket, entry_ref) do + Enum.find(socket.assigns.uploads.mp3.entries, fn entry -> entry.ref == entry_ref end) || + raise "no entry found for ref #{inspect(entry_ref)}" end end diff --git a/lib/live_beats_web/live/song_live/upload_form_component.html.heex b/lib/live_beats_web/live/song_live/upload_form_component.html.heex index 7ddaf3d..d0de3ab 100644 --- a/lib/live_beats_web/live/song_live/upload_form_component.html.heex +++ b/lib/live_beats_web/live/song_live/upload_form_component.html.heex @@ -10,57 +10,57 @@ phx-submit="save">
-
- <%= for {ref, changeset} <- @changesets do %> - <.live_component id={ref} module={SongEntry} changeset={changeset} /> - <% end %> +
+ <%= for {ref, changeset} <- @changesets do %> + <.live_component id={ref} module={SongEntryComponent} changeset={changeset} /> + <% end %> - -
-
- <%= if Enum.any?(@error_messages) do %> -
-
-
+ +
+
+ <%= if Enum.any?(@error_messages) do %> +
+
+
<.icon name={:x_circle} class="h-5 w-5 text-red-400"/> -
-
-

- Oops! -

-
-
    - <%= for {client_name, error} <- @error_messages do %> -
  • <%= client_name %>: <.file_error kind={error} />
  • - <% end %> -
-
-
-
-
- <% end %> +
+
+

+ Oops! +

+
+
    + <%= for {client_name, kind} <- @error_messages do %> +
  • <%= client_name %>: <.file_error kind={kind} />
  • + <% end %> +
+
+
+
+
+ <% end %> -
-
- -
- -

or drag and drop

-
-

- MP3s up to 20MB -

-
-
-
-
- -
+
+
+ +
+ +

or drag and drop

+
+

+ MP3s up to 20MB +

+
+
+
+
+ +
diff --git a/lib/live_beats_web/views/error_helpers.ex b/lib/live_beats_web/views/error_helpers.ex index 4d6904e..e30f968 100644 --- a/lib/live_beats_web/views/error_helpers.ex +++ b/lib/live_beats_web/views/error_helpers.ex @@ -4,25 +4,38 @@ defmodule LiveBeatsWeb.ErrorHelpers do """ use Phoenix.HTML + import Phoenix.LiveView + import Phoenix.LiveView.Helpers @doc """ Generates tag for inlined form input errors. """ def error_tag(form, field) do - error_tag(form.errors, field, input_name(form, field)) + error(%{errors: form.errors, field: field, input_name: input_name(form, field)}) end - def error_tag(errors, field, input_name) do - Enum.map(Keyword.get_values(errors, field), fn error -> - content_tag(:div, translate_error(error), - class: "invalid-feedback mt-0 text-sm text-red-600 text-right", - phx_feedback_for: input_name - ) - end) + def error(%{errors: errors, field: field} = assigns) do + assigns = + assigns + |> assign(:error_values, Keyword.get_values(errors, field)) + |> assign_new(:class, fn -> "" end) + + ~H""" + <%= for error <- @error_values do %> +
+ <%= translate_error(error) %> +
+ <% end %> + + <%= if Enum.empty?(@error_values) do %> +
+ <% end %> + """ end - - @doc """ Translates an error message using gettext. """