diff --git a/assets/js/app.js b/assets/js/app.js index 04e6408..dfd4b5a 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1,6 +1,7 @@ import "phoenix_html" import {Socket} from "phoenix" -import {LiveSocket} from "phoenix_live_view" +// 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" let nowSeconds = () => Math.round(Date.now() / 1000) @@ -13,6 +14,17 @@ let execJS = (selector, attr) => { let Hooks = {} +Hooks.Sortable = { + mounted(){ + let sorter = new Sortable(this.el, { + animation: 150, + onEnd: e => { + this.pushEvent(this.el.dataset["drop"], {id: e.item.id, old: e.oldIndex, new: e.newIndex}) + } + }) + } +} + Hooks.Flash = { mounted(){ let hide = () => liveSocket.execJS(this.el, this.el.getAttribute("phx-click")) @@ -318,4 +330,4 @@ liveSocket.connect() // >> liveSocket.enableDebug() // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.disableLatencySim() -window.liveSocket = liveSocket \ No newline at end of file +window.liveSocket = liveSocket diff --git a/lib/live_beats/media_library.ex b/lib/live_beats/media_library.ex index b8f9193..f1586d3 100644 --- a/lib/live_beats/media_library.ex +++ b/lib/live_beats/media_library.ex @@ -162,12 +162,15 @@ defmodule LiveBeats.MediaLibrary do user = Accounts.get_user!(user.id) multi = - Enum.reduce(changesets, Ecto.Multi.new(), fn {ref, chset}, acc -> + changesets + |> Enum.with_index() + |> Enum.reduce(Ecto.Multi.new(), fn {{ref, chset}, idx}, acc -> chset = 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) end) @@ -241,7 +244,7 @@ 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) + from(s in Song, where: s.user_id == ^profile.user_id, limit: ^limit, order_by: [asc: :position]) |> order_by_playlist(:asc) |> Repo.replica().all() end @@ -342,6 +345,48 @@ defmodule LiveBeats.MediaLibrary do prev || get_last_song(profile) end + def update_song_position(%Song{} = song, new_index) do + old_index = song.position + + multi = + Ecto.Multi.new() + |> Ecto.Multi.run(:valid_index, fn repo, _changes -> + case repo.one(from s in Song, where: s.user_id == ^song.user_id, select: count(s.id)) do + count when new_index < count -> {:ok, count} + _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, []) + + case LiveBeats.Repo.transaction(multi) do + {:ok, _} -> + broadcast!(song.user_id, %Events.NewPosition{song: %Song{song | position: new_index}}) + :ok + + {:error, failed_op, _failed_val, _changes} -> + {:error, failed_op} + end + end + def update_song(%Song{} = song, attrs) do song |> Song.changeset(attrs) diff --git a/lib/live_beats/media_library/events.ex b/lib/live_beats/media_library/events.ex index 36739ff..ab0341a 100644 --- a/lib/live_beats/media_library/events.ex +++ b/lib/live_beats/media_library/events.ex @@ -14,4 +14,8 @@ defmodule LiveBeats.MediaLibrary.Events do defmodule SongsImported do defstruct user_id: nil, songs: [] end + + defmodule NewPosition do + defstruct song: nil + end end diff --git a/lib/live_beats/media_library/song.ex b/lib/live_beats/media_library/song.ex index ac68750..e17c406 100644 --- a/lib/live_beats/media_library/song.ex +++ b/lib/live_beats/media_library/song.ex @@ -21,6 +21,7 @@ defmodule LiveBeats.MediaLibrary.Song do field :mp3_filename, :string field :mp3_filesize, :integer, default: 0 field :server_ip, EctoNetwork.INET + field :position, :integer, default: 0 belongs_to :user, Accounts.User belongs_to :genre, LiveBeats.MediaLibrary.Genre diff --git a/lib/live_beats_web/components/core_components.ex b/lib/live_beats_web/components/core_components.ex index 29c67cd..1ffa7fd 100644 --- a/lib/live_beats_web/components/core_components.ex +++ b/lib/live_beats_web/components/core_components.ex @@ -189,6 +189,7 @@ defmodule LiveBeatsWeb.CoreComponents do slot :title slot :subtitle + slot :link do attr :navigate, :string attr :href, :string @@ -550,21 +551,24 @@ defmodule LiveBeatsWeb.CoreComponents do """ end + attr :id, :any, default: nil attr :row_id, :any, default: false + attr :row_click, :any, default: nil attr :rows, :list, required: true + attr :streamable, :boolean, default: false + attr :sortable_drop, :string, default: nil - slot :col, required: true + slot :col, required: true do + attr :label, :string + attr :class, :string + attr :class!, :string + end def table(assigns) do - assigns = - assigns - |> assign_new(:row_id, fn -> false end) - |> assign(:col, for(col <- assigns.col, col[:if] != false, do: col)) - ~H""" -