This commit is contained in:
Chris McCord 2023-01-26 19:39:59 -05:00
parent b676f7aeb4
commit 7b43323813
6 changed files with 88 additions and 53 deletions

View file

@ -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)

View file

@ -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

View file

@ -18,4 +18,8 @@ defmodule LiveBeats.MediaLibrary.Events do
defmodule NewPosition do
defstruct song: nil
end
defmodule SongDeleted do
defstruct song: nil
end
end

View file

@ -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 %>
<tr id={@row_id && @row_id.(row)} class="hover:bg-gray-50">
<%= for col <- @col do %>
<td
phx-click={@row_click && @row_click.(row)}
class={
col[:class!] ||
"px-6 py-3 whitespace-nowrap text-sm font-medium text-gray-900 #{if i == 0, do: "max-w-0 w-full"} #{col[:class]}"
}
>
<div class="flex items-center space-x-3 lg:pl-2">
<%= render_slot(col, row) %>
</div>
</td>
<% end %>
</tr>
<% end %>
<tr
:for={{row, i} <- Enum.with_index(@rows)}
id={@row_id && @row_id.(row)}
phx-remove={@row_remove && @row_remove.(row)}
class="hover:bg-gray-50"
>
<td
:for={col <- @col}
phx-click={@row_click && @row_click.(row)}
class={
col[:class!] ||
"px-6 py-3 whitespace-nowrap text-sm font-medium text-gray-900 #{if i == 0, do: "max-w-0 w-full"} #{col[:class]}"
}
>
<div class="flex items-center space-x-3 lg:pl-2">
<%= render_slot(col, row) %>
</div>
</td>
</tr>
</tbody>
</table>
</div>

View file

@ -9,7 +9,6 @@
<%= assigns[:page_title] || "LiveBeats" %>
</.live_title>
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
<script defer type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.15.0/Sortable.min.js"></script>
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}></script>
</head>
<body>

View file

@ -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"
/>
</span>
<%= song.title %> <%= @count %>
<%= song.title %>
</:col>
<:col :let={{_id, song}} label="Artist"><%= song.artist %></:col>
<: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