diff --git a/assets/js/app.js b/assets/js/app.js index dfd4b5a..775d85d 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -3,6 +3,7 @@ import {Socket} from "phoenix" // import {LiveSocket} from "phoenix_live_view" import {LiveSocket} from "/Users/chris/oss/phoenix_live_view/assets/js/phoenix_live_view" import topbar from "../vendor/topbar" +import Sortable from "../vendor/sortable" let nowSeconds = () => Math.round(Date.now() / 1000) let rand = (min, max) => Math.floor(Math.random() * (max - min) + min) diff --git a/lib/live_beats/media_library.ex b/lib/live_beats/media_library.ex index f1586d3..5fee975 100644 --- a/lib/live_beats/media_library.ex +++ b/lib/live_beats/media_library.ex @@ -161,18 +161,24 @@ defmodule LiveBeats.MediaLibrary do # refetch user for fresh song count user = Accounts.get_user!(user.id) + multi = + Ecto.Multi.new() + |> Ecto.Multi.run(:starting_position, fn repo, _changes -> + count = repo.one(from s in Song, where: s.user_id == ^user.id, select: count(s.id)) + {:ok, count - 1} + end) + multi = changesets |> Enum.with_index() - |> Enum.reduce(Ecto.Multi.new(), fn {{ref, chset}, idx}, acc -> - chset = + |> Enum.reduce(multi, fn {{ref, chset}, i}, acc -> + Ecto.Multi.insert(acc, {:song, ref}, fn %{starting_position: pos_start} -> chset |> Song.put_user(user) |> Song.put_mp3_path() |> Song.put_server_ip() - |> Ecto.Changeset.put_change(:position, idx) - - Ecto.Multi.insert(acc, {:song, ref}, chset) + |> Ecto.Changeset.put_change(:position, pos_start + i + 1) + end) end) |> Ecto.Multi.run(:valid_songs_count, fn _repo, changes -> new_songs_count = changes |> Enum.filter(&match?({{:song, _ref}, _}, &1)) |> Enum.count() @@ -244,7 +250,10 @@ defmodule LiveBeats.MediaLibrary do end def list_profile_songs(%Profile{} = profile, limit \\ 100) do - from(s in Song, where: s.user_id == ^profile.user_id, limit: ^limit, order_by: [asc: :position]) + from(s in Song, + where: s.user_id == ^profile.user_id, + limit: ^limit + ) |> order_by_playlist(:asc) |> Repo.replica().all() end @@ -323,7 +332,7 @@ defmodule LiveBeats.MediaLibrary do def get_next_song(%Song{} = song, %Profile{} = profile) do next = from(s in Song, - where: s.user_id == ^song.user_id and s.id > ^song.id, + where: s.user_id == ^song.user_id and s.position > ^song.position, limit: 1 ) |> order_by_playlist(:asc) @@ -335,8 +344,7 @@ defmodule LiveBeats.MediaLibrary do def get_prev_song(%Song{} = song, %Profile{} = profile) do prev = from(s in Song, - where: s.user_id == ^song.user_id and s.id < ^song.id, - order_by: [desc: s.inserted_at, desc: s.id], + where: s.user_id == ^song.user_id and s.position < ^song.position, limit: 1 ) |> order_by_playlist(:desc) @@ -356,26 +364,26 @@ defmodule LiveBeats.MediaLibrary do _count -> {:error, :index_out_of_range} end end) - |> Ecto.Multi.update_all(:dec_positions, fn _ -> - from(s in Song, - where: s.user_id == ^song.user_id and s.id != ^song.id, - where: s.position > ^old_index and s.position <= ^new_index, - update: [inc: [position: -1]] - ) - end, []) - |> Ecto.Multi.update_all(:inc_positions, fn _ -> - from(s in Song, - where: s.user_id == ^song.user_id and s.id != ^song.id, - where: s.position < ^old_index and s.position >= ^new_index, - update: [inc: [position: 1]] - ) - end, []) - |> Ecto.Multi.update_all(:position, fn _ -> - from(s in Song, - where: s.id == ^song.id, - update: [set: [position: ^new_index]] - ) - end, []) + |> multi_update_all(:dec_positions, fn _ -> + from(s in Song, + where: s.user_id == ^song.user_id and s.id != ^song.id, + where: s.position > ^old_index and s.position <= ^new_index, + update: [inc: [position: -1]] + ) + end) + |> multi_update_all(:inc_positions, fn _ -> + from(s in Song, + where: s.user_id == ^song.user_id and s.id != ^song.id, + where: s.position < ^old_index and s.position >= ^new_index, + update: [inc: [position: 1]] + ) + end) + |> multi_update_all(:position, fn _ -> + from(s in Song, + where: s.id == ^song.id, + update: [set: [position: ^new_index]] + ) + end) case LiveBeats.Repo.transaction(multi) do {:ok, _} -> @@ -395,14 +403,26 @@ defmodule LiveBeats.MediaLibrary do def delete_song(%Song{} = song) do delete_song_file(song) + old_index = song.position Ecto.Multi.new() |> Ecto.Multi.delete(:delete, song) + |> multi_update_all(:dec_positions, fn _ -> + from(s in Song, + where: s.user_id == ^song.user_id, + where: s.position > ^old_index, + update: [inc: [position: -1]] + ) + end) |> update_user_songs_count(song.user_id, -1) |> Repo.transaction() |> case do - {:ok, _} -> :ok - other -> other + {:ok, _} -> + broadcast!(song.user_id, %Events.SongDeleted{song: song}) + :ok + + other -> + other end end @@ -493,7 +513,7 @@ defmodule LiveBeats.MediaLibrary do end defp order_by_playlist(%Ecto.Query{} = query, direction) when direction in [:asc, :desc] do - from(s in query, order_by: [{^direction, s.inserted_at}, {^direction, s.id}]) + from(s in query, order_by: [{^direction, s.position}]) end defp broadcast!(user_id, msg) when is_integer(user_id) do @@ -509,4 +529,8 @@ defmodule LiveBeats.MediaLibrary do {:error, :songs_limit_exceeded} end end + + defp multi_update_all(multi, name, func, opts \\ []) do + Ecto.Multi.update_all(multi, name, func, opts) + end end diff --git a/lib/live_beats/media_library/events.ex b/lib/live_beats/media_library/events.ex index ab0341a..724c3ee 100644 --- a/lib/live_beats/media_library/events.ex +++ b/lib/live_beats/media_library/events.ex @@ -18,4 +18,8 @@ defmodule LiveBeats.MediaLibrary.Events do defmodule NewPosition do defstruct song: nil end + + defmodule SongDeleted do + defstruct song: nil + end end diff --git a/lib/live_beats_web/components/core_components.ex b/lib/live_beats_web/components/core_components.ex index 1ffa7fd..a4e9644 100644 --- a/lib/live_beats_web/components/core_components.ex +++ b/lib/live_beats_web/components/core_components.ex @@ -554,6 +554,7 @@ defmodule LiveBeatsWeb.CoreComponents do attr :id, :any, default: nil attr :row_id, :any, default: false attr :row_click, :any, default: nil + attr :row_remove, :any, default: nil attr :rows, :list, required: true attr :streamable, :boolean, default: false attr :sortable_drop, :string, default: nil @@ -585,23 +586,25 @@ defmodule LiveBeatsWeb.CoreComponents do phx-hook={@sortable_drop && "Sortable"} data-drop={@sortable_drop} > - <%= for {row, i} <- Enum.with_index(@rows) do %> - - <%= for col <- @col do %> - -
- <%= render_slot(col, row) %> -
- - <% end %> - - <% end %> + + +
+ <%= render_slot(col, row) %> +
+ + diff --git a/lib/live_beats_web/components/layouts/root.html.heex b/lib/live_beats_web/components/layouts/root.html.heex index 2dd3952..3e472e8 100644 --- a/lib/live_beats_web/components/layouts/root.html.heex +++ b/lib/live_beats_web/components/layouts/root.html.heex @@ -9,7 +9,6 @@ <%= assigns[:page_title] || "LiveBeats" %> - diff --git a/lib/live_beats_web/live/profile_live.ex b/lib/live_beats_web/live/profile_live.ex index b9421df..b9f1f21 100644 --- a/lib/live_beats_web/live/profile_live.ex +++ b/lib/live_beats_web/live/profile_live.ex @@ -83,6 +83,7 @@ defmodule LiveBeatsWeb.ProfileLive do rows={@songs} row_id={fn {id, _song} -> id end} row_click={fn {_id, song} -> JS.push("play_or_pause", value: %{id: song.id}) end} + row_remove={fn {id, _song} -> hide("##{id}") end} streamable sortable_drop="row_dropped" > @@ -108,7 +109,7 @@ defmodule LiveBeatsWeb.ProfileLive do role="button" /> - <%= song.title %> <%= @count %> + <%= song.title %> <:col :let={{_id, song}} label="Artist"><%= song.artist %> <:col @@ -153,7 +154,6 @@ defmodule LiveBeatsWeb.ProfileLive do socket = socket |> assign( - count: 0, active_song_id: active_song_id, active_profile_id: current_user.active_profile_user_id, profile: profile, @@ -247,7 +247,11 @@ defmodule LiveBeatsWeb.ProfileLive do end def handle_info({MediaLibrary, %MediaLibrary.Events.SongsImported{songs: songs}}, socket) do - {:noreply, update(socket, :songs, &(&1 ++ songs))} + {:noreply, Enum.reduce(songs, socket, fn song, acc -> stream_insert(acc, :songs, song) end)} + end + + def handle_info({MediaLibrary, %MediaLibrary.Events.SongDeleted{song: song}}, socket) do + {:noreply, stream_delete(socket, :songs, song)} end def handle_info({MediaLibrary, {:ping, ping}}, socket) do