Reorder record sets via drag and drop
This commit is contained in:
@@ -26,6 +26,7 @@ import { Hooks as FluxonHooks, DOM as FluxonDOM } from "fluxon";
|
|||||||
import FormatNumberHook from "./hooks/format-number";
|
import FormatNumberHook from "./hooks/format-number";
|
||||||
import UniversalSearchNavigationHook from "./hooks/universal-search-navigation";
|
import UniversalSearchNavigationHook from "./hooks/universal-search-navigation";
|
||||||
import RecordPickerNavigationHook from "./hooks/record-picker-navigation";
|
import RecordPickerNavigationHook from "./hooks/record-picker-navigation";
|
||||||
|
import SortableListHook from "./hooks/sortable-list";
|
||||||
import confetti from "canvas-confetti";
|
import confetti from "canvas-confetti";
|
||||||
import { createLiveToastHook } from "live_toast";
|
import { createLiveToastHook } from "live_toast";
|
||||||
import banner from "./banner";
|
import banner from "./banner";
|
||||||
@@ -34,6 +35,7 @@ let Hooks = FluxonHooks;
|
|||||||
Hooks.FormatNumber = FormatNumberHook;
|
Hooks.FormatNumber = FormatNumberHook;
|
||||||
Hooks.UniversalSearchNavigation = UniversalSearchNavigationHook;
|
Hooks.UniversalSearchNavigation = UniversalSearchNavigationHook;
|
||||||
Hooks.RecordPickerNavigation = RecordPickerNavigationHook;
|
Hooks.RecordPickerNavigation = RecordPickerNavigationHook;
|
||||||
|
Hooks.SortableList = SortableListHook;
|
||||||
Hooks.LiveToast = createLiveToastHook();
|
Hooks.LiveToast = createLiveToastHook();
|
||||||
|
|
||||||
const csrfToken = document
|
const csrfToken = document
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import Sortable from "sortablejs";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mounted() {
|
||||||
|
this.initSortable();
|
||||||
|
},
|
||||||
|
|
||||||
|
updated() {
|
||||||
|
if (this.sortable) {
|
||||||
|
this.sortable.destroy();
|
||||||
|
}
|
||||||
|
this.initSortable();
|
||||||
|
},
|
||||||
|
|
||||||
|
destroyed() {
|
||||||
|
if (this.sortable) {
|
||||||
|
this.sortable.destroy();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
initSortable() {
|
||||||
|
this.sortable = Sortable.create(this.el, {
|
||||||
|
animation: 150,
|
||||||
|
handle: "[data-sortable-handle]",
|
||||||
|
draggable: "[data-sortable-item]",
|
||||||
|
ghostClass: "opacity-30",
|
||||||
|
dragClass: "shadow-lg",
|
||||||
|
onEnd: () => {
|
||||||
|
const items = this.el.querySelectorAll("[data-sortable-item]");
|
||||||
|
const recordIds = Array.from(items).map(
|
||||||
|
(item) => item.dataset.recordId
|
||||||
|
);
|
||||||
|
|
||||||
|
const payload = { record_ids: recordIds };
|
||||||
|
const setId = this.el.dataset.setId;
|
||||||
|
if (setId) {
|
||||||
|
payload.set_id = setId;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pushEvent("reorder", payload);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
Generated
+8
-1
@@ -7,7 +7,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"barcode-detector": "^3.0.0",
|
"barcode-detector": "^3.0.0",
|
||||||
"canvas-confetti": "^1.9.3",
|
"canvas-confetti": "^1.9.3",
|
||||||
"live_toast": "file:../deps/live_toast"
|
"live_toast": "file:../deps/live_toast",
|
||||||
|
"sortablejs": "^1.15.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.16"
|
"@tailwindcss/typography": "^0.5.16"
|
||||||
@@ -86,6 +87,12 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sortablejs": {
|
||||||
|
"version": "1.15.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz",
|
||||||
|
"integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tagged-tag": {
|
"node_modules/tagged-tag": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
|
||||||
|
|||||||
+2
-1
@@ -2,7 +2,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"barcode-detector": "^3.0.0",
|
"barcode-detector": "^3.0.0",
|
||||||
"canvas-confetti": "^1.9.3",
|
"canvas-confetti": "^1.9.3",
|
||||||
"live_toast": "file:../deps/live_toast"
|
"live_toast": "file:../deps/live_toast",
|
||||||
|
"sortablejs": "^1.15.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.16"
|
"@tailwindcss/typography": "^0.5.16"
|
||||||
|
|||||||
@@ -116,6 +116,22 @@ defmodule MusicLibrary.RecordSets do
|
|||||||
{:ok, get_record_set!(record_set.id)}
|
{:ok, get_record_set!(record_set.id)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reorder_records_in_set(%RecordSet{} = record_set, ordered_record_ids)
|
||||||
|
when is_list(ordered_record_ids) do
|
||||||
|
Repo.transaction(fn ->
|
||||||
|
ordered_record_ids
|
||||||
|
|> Enum.with_index()
|
||||||
|
|> Enum.each(fn {record_id, position} ->
|
||||||
|
from(i in RecordSetItem,
|
||||||
|
where: i.record_set_id == ^record_set.id and i.record_id == ^record_id
|
||||||
|
)
|
||||||
|
|> Repo.update_all(set: [position: position])
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, get_record_set!(record_set.id)}
|
||||||
|
end
|
||||||
|
|
||||||
def move_record_in_set(%RecordSet{} = record_set, record_id, direction)
|
def move_record_in_set(%RecordSet{} = record_set, record_id, direction)
|
||||||
when direction in [:up, :down] do
|
when direction in [:up, :down] do
|
||||||
items =
|
items =
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ defmodule MusicLibraryWeb.RecordSetLive.Index do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp apply_fallback_index(socket, params) do
|
defp apply_fallback_index(socket, params) do
|
||||||
if get_in(socket.assigns, [:streams, :record_sets]) == nil do
|
if socket.assigns[:record_sets] == nil do
|
||||||
socket
|
socket
|
||||||
|> apply_action(:index, params)
|
|> apply_action(:index, params)
|
||||||
else
|
else
|
||||||
@@ -84,7 +84,7 @@ defmodule MusicLibraryWeb.RecordSetLive.Index do
|
|||||||
|> assign(:list_params, list_params)
|
|> assign(:list_params, list_params)
|
||||||
|> assign(:page_title, gettext("Record Sets"))
|
|> assign(:page_title, gettext("Record Sets"))
|
||||||
|> assign(:record_set, nil)
|
|> assign(:record_set, nil)
|
||||||
|> stream(:record_sets, sets, reset: true)
|
|> assign(:record_sets, sets)
|
||||||
end
|
end
|
||||||
|
|
||||||
def back_path(list_params) do
|
def back_path(list_params) do
|
||||||
@@ -98,29 +98,24 @@ defmodule MusicLibraryWeb.RecordSetLive.Index do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info(
|
def handle_info(
|
||||||
{MusicLibraryWeb.RecordSetLive.Form, {:created, record_set}},
|
{MusicLibraryWeb.RecordSetLive.Form, {:created, _record_set}},
|
||||||
socket
|
socket
|
||||||
) do
|
) do
|
||||||
{:noreply,
|
{:noreply, load_and_assign_sets(socket, socket.assigns.list_params)}
|
||||||
socket
|
|
||||||
|> stream_insert(:record_sets, record_set, at: 0)
|
|
||||||
|> load_and_assign_sets(socket.assigns.list_params)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(
|
def handle_info(
|
||||||
{MusicLibraryWeb.RecordSetLive.Form, {:updated, record_set}},
|
{MusicLibraryWeb.RecordSetLive.Form, {:updated, record_set}},
|
||||||
socket
|
socket
|
||||||
) do
|
) do
|
||||||
{:noreply, stream_insert(socket, :record_sets, record_set)}
|
{:noreply, update_record_set_in_list(socket, record_set)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(
|
def handle_info(
|
||||||
{MusicLibraryWeb.RecordSetLive.RecordPicker, {:added, record_set}},
|
{MusicLibraryWeb.RecordSetLive.RecordPicker, {:added, record_set}},
|
||||||
socket
|
socket
|
||||||
) do
|
) do
|
||||||
{:noreply,
|
{:noreply, update_record_set_in_list(socket, record_set)}
|
||||||
socket
|
|
||||||
|> stream_insert(:record_sets, record_set)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@@ -138,31 +133,44 @@ defmodule MusicLibraryWeb.RecordSetLive.Index do
|
|||||||
record_set = RecordSets.get_record_set!(id)
|
record_set = RecordSets.get_record_set!(id)
|
||||||
{:ok, _} = RecordSets.delete_record_set(record_set)
|
{:ok, _} = RecordSets.delete_record_set(record_set)
|
||||||
|
|
||||||
{:noreply,
|
{:noreply, load_and_assign_sets(socket, socket.assigns.list_params)}
|
||||||
socket
|
|
||||||
|> stream_delete(:record_sets, record_set)
|
|
||||||
|> load_and_assign_sets(socket.assigns.list_params)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("remove_record", %{"set-id" => set_id, "record-id" => record_id}, socket) do
|
def handle_event("remove_record", %{"set-id" => set_id, "record-id" => record_id}, socket) do
|
||||||
record_set = RecordSets.get_record_set!(set_id)
|
record_set = RecordSets.get_record_set!(set_id)
|
||||||
{:ok, updated_set} = RecordSets.remove_record_from_set(record_set, record_id)
|
{:ok, updated_set} = RecordSets.remove_record_from_set(record_set, record_id)
|
||||||
|
|
||||||
{:noreply, stream_insert(socket, :record_sets, updated_set)}
|
{:noreply, update_record_set_in_list(socket, updated_set)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("move_up", %{"set-id" => set_id, "record-id" => record_id}, socket) do
|
def handle_event("move_up", %{"set-id" => set_id, "record-id" => record_id}, socket) do
|
||||||
record_set = RecordSets.get_record_set!(set_id)
|
record_set = RecordSets.get_record_set!(set_id)
|
||||||
{:ok, updated_set} = RecordSets.move_record_in_set(record_set, record_id, :up)
|
{:ok, updated_set} = RecordSets.move_record_in_set(record_set, record_id, :up)
|
||||||
|
|
||||||
{:noreply, stream_insert(socket, :record_sets, updated_set)}
|
{:noreply, update_record_set_in_list(socket, updated_set)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("move_down", %{"set-id" => set_id, "record-id" => record_id}, socket) do
|
def handle_event("move_down", %{"set-id" => set_id, "record-id" => record_id}, socket) do
|
||||||
record_set = RecordSets.get_record_set!(set_id)
|
record_set = RecordSets.get_record_set!(set_id)
|
||||||
{:ok, updated_set} = RecordSets.move_record_in_set(record_set, record_id, :down)
|
{:ok, updated_set} = RecordSets.move_record_in_set(record_set, record_id, :down)
|
||||||
|
|
||||||
{:noreply, stream_insert(socket, :record_sets, updated_set)}
|
{:noreply, update_record_set_in_list(socket, updated_set)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("reorder", %{"set_id" => set_id, "record_ids" => record_ids}, socket) do
|
||||||
|
record_set = RecordSets.get_record_set!(set_id)
|
||||||
|
{:ok, updated_set} = RecordSets.reorder_records_in_set(record_set, record_ids)
|
||||||
|
|
||||||
|
{:noreply, update_record_set_in_list(socket, updated_set)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp update_record_set_in_list(socket, updated_set) do
|
||||||
|
record_sets =
|
||||||
|
Enum.map(socket.assigns.record_sets, fn set ->
|
||||||
|
if set.id == updated_set.id, do: updated_set, else: set
|
||||||
|
end)
|
||||||
|
|
||||||
|
assign(socket, :record_sets, record_sets)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_order("alphabetical"), do: :alphabetical
|
defp parse_order("alphabetical"), do: :alphabetical
|
||||||
|
|||||||
@@ -35,10 +35,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 space-y-6">
|
<div class="mt-6 space-y-6">
|
||||||
<ul phx-update="stream" id="record-sets-list" class="space-y-6">
|
<ul id="record-sets-list" class="space-y-6">
|
||||||
<li
|
<li
|
||||||
|
:if={@record_sets == []}
|
||||||
id="no-record-sets"
|
id="no-record-sets"
|
||||||
class="hidden only:block p-8 text-center bg-zinc-50 dark:bg-zinc-800 rounded-lg"
|
class="p-8 text-center bg-zinc-50 dark:bg-zinc-800 rounded-lg"
|
||||||
>
|
>
|
||||||
<.icon name="hero-rectangle-stack" class="h-12 w-12 text-zinc-400 mx-auto mb-4" />
|
<.icon name="hero-rectangle-stack" class="h-12 w-12 text-zinc-400 mx-auto mb-4" />
|
||||||
<p class="text-zinc-600 dark:text-zinc-400">
|
<p class="text-zinc-600 dark:text-zinc-400">
|
||||||
@@ -47,8 +48,8 @@
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
:for={{dom_id, record_set} <- @streams.record_sets}
|
:for={record_set <- @record_sets}
|
||||||
id={dom_id}
|
id={"record-set-#{record_set.id}"}
|
||||||
class="bg-white dark:bg-zinc-900 rounded-lg border border-zinc-200 dark:border-zinc-700 p-4"
|
class="bg-white dark:bg-zinc-900 rounded-lg border border-zinc-200 dark:border-zinc-700 p-4"
|
||||||
>
|
>
|
||||||
<div class="flex items-baseline justify-between mb-3">
|
<div class="flex items-baseline justify-between mb-3">
|
||||||
@@ -106,9 +107,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-start gap-3 md:overflow-x-auto pb-2">
|
<div
|
||||||
|
class="flex flex-wrap items-start gap-3 md:overflow-x-auto pb-2"
|
||||||
|
id={"record-set-#{record_set.id}-items"}
|
||||||
|
phx-hook="SortableList"
|
||||||
|
data-set-id={record_set.id}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
:for={item <- record_set.items}
|
:for={item <- record_set.items}
|
||||||
|
data-sortable-item
|
||||||
|
data-record-id={item.record.id}
|
||||||
class={[
|
class={[
|
||||||
"flex-none w-24 sm:w-32 group relative",
|
"flex-none w-24 sm:w-32 group relative",
|
||||||
is_nil(item.record.purchased_at) &&
|
is_nil(item.record.purchased_at) &&
|
||||||
@@ -135,6 +143,16 @@
|
|||||||
width={256}
|
width={256}
|
||||||
/>
|
/>
|
||||||
</.link>
|
</.link>
|
||||||
|
<div
|
||||||
|
data-sortable-handle
|
||||||
|
class="absolute top-1 left-1 hidden group-hover:flex items-center justify-center rounded-full bg-zinc-100/50 hover:bg-zinc-100/75 dark:bg-zinc-700/50 dark:hover:bg-zinc-700/75 size-5 cursor-grab active:cursor-grabbing"
|
||||||
|
>
|
||||||
|
<.icon
|
||||||
|
name="hero-bars-2"
|
||||||
|
class="size-3.5 text-zinc-800 dark:text-zinc-200"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="absolute top-1 right-1 rounded-full bg-zinc-100/50 hover:bg-zinc-100/75 dark:bg-zinc-700/50 dark:hover:bg-zinc-700/75 size-5">
|
<div class="absolute top-1 right-1 rounded-full bg-zinc-100/50 hover:bg-zinc-100/75 dark:bg-zinc-700/50 dark:hover:bg-zinc-700/75 size-5">
|
||||||
<.dropdown
|
<.dropdown
|
||||||
id={"item-actions-#{record_set.id}-#{item.record.id}"}
|
id={"item-actions-#{record_set.id}-#{item.record.id}"}
|
||||||
|
|||||||
@@ -76,6 +76,13 @@ defmodule MusicLibraryWeb.RecordSetLive.Show do
|
|||||||
{:noreply, assign(socket, :record_set, updated_set)}
|
{:noreply, assign(socket, :record_set, updated_set)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_event("reorder", %{"record_ids" => record_ids}, socket) do
|
||||||
|
{:ok, updated_set} =
|
||||||
|
RecordSets.reorder_records_in_set(socket.assigns.record_set, record_ids)
|
||||||
|
|
||||||
|
{:noreply, assign(socket, :record_set, updated_set)}
|
||||||
|
end
|
||||||
|
|
||||||
defp page_title(:show, record_set), do: record_set.name
|
defp page_title(:show, record_set), do: record_set.name
|
||||||
defp page_title(:edit, record_set), do: gettext("Edit") <> " · " <> record_set.name
|
defp page_title(:edit, record_set), do: gettext("Edit") <> " · " <> record_set.name
|
||||||
defp page_title(:add_record, record_set), do: gettext("Add Record") <> " · " <> record_set.name
|
defp page_title(:add_record, record_set), do: gettext("Add Record") <> " · " <> record_set.name
|
||||||
|
|||||||
@@ -73,10 +73,13 @@
|
|||||||
:if={@record_set.items != []}
|
:if={@record_set.items != []}
|
||||||
class="grid grid-cols-2 sm:grid-cols-4 gap-4"
|
class="grid grid-cols-2 sm:grid-cols-4 gap-4"
|
||||||
id="record-set-records"
|
id="record-set-records"
|
||||||
|
phx-hook="SortableList"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:for={item <- @record_set.items}
|
:for={item <- @record_set.items}
|
||||||
id={"record-#{item.record.id}"}
|
id={"record-#{item.record.id}"}
|
||||||
|
data-sortable-item
|
||||||
|
data-record-id={item.record.id}
|
||||||
class={[
|
class={[
|
||||||
"group relative",
|
"group relative",
|
||||||
is_nil(item.record.purchased_at) &&
|
is_nil(item.record.purchased_at) &&
|
||||||
@@ -103,6 +106,16 @@
|
|||||||
width={512}
|
width={512}
|
||||||
/>
|
/>
|
||||||
</.link>
|
</.link>
|
||||||
|
<div
|
||||||
|
data-sortable-handle
|
||||||
|
class="absolute top-1 left-1 hidden group-hover:flex items-center justify-center rounded-full bg-zinc-100/50 hover:bg-zinc-100/75 dark:bg-zinc-700/50 dark:hover:bg-zinc-700/75 size-5 cursor-grab active:cursor-grabbing"
|
||||||
|
>
|
||||||
|
<.icon
|
||||||
|
name="hero-bars-2"
|
||||||
|
class="size-3.5 text-zinc-800 dark:text-zinc-200"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="absolute top-1 right-1 rounded-full bg-zinc-100/50 hover:bg-zinc-100/75 dark:bg-zinc-700/50 dark:hover:bg-zinc-700/75 size-5">
|
<div class="absolute top-1 right-1 rounded-full bg-zinc-100/50 hover:bg-zinc-100/75 dark:bg-zinc-700/50 dark:hover:bg-zinc-700/75 size-5">
|
||||||
<.dropdown
|
<.dropdown
|
||||||
id={"item-actions-#{item.record.id}"}
|
id={"item-actions-#{item.record.id}"}
|
||||||
|
|||||||
@@ -176,6 +176,26 @@ defmodule MusicLibrary.RecordSetsTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "reorder_records_in_set/2" do
|
||||||
|
test "reorders records according to the given order" do
|
||||||
|
{set, [r1, r2, r3]} = record_set_with_records(3)
|
||||||
|
|
||||||
|
{:ok, updated} = RecordSets.reorder_records_in_set(set, [r3.id, r1.id, r2.id])
|
||||||
|
|
||||||
|
ids_in_order = Enum.map(updated.items, & &1.record.id)
|
||||||
|
assert ids_in_order == [r3.id, r1.id, r2.id]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "no-ops when order is unchanged" do
|
||||||
|
{set, [r1, r2, r3]} = record_set_with_records(3)
|
||||||
|
|
||||||
|
{:ok, updated} = RecordSets.reorder_records_in_set(set, [r1.id, r2.id, r3.id])
|
||||||
|
|
||||||
|
ids_in_order = Enum.map(updated.items, & &1.record.id)
|
||||||
|
assert ids_in_order == [r1.id, r2.id, r3.id]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "move_record_in_set/3" do
|
describe "move_record_in_set/3" do
|
||||||
test "moves a record up" do
|
test "moves a record up" do
|
||||||
{set, [r1, r2 | _]} = record_set_with_records(3)
|
{set, [r1, r2 | _]} = record_set_with_records(3)
|
||||||
|
|||||||
@@ -119,6 +119,23 @@ defmodule MusicLibraryWeb.RecordSetLive.IndexTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "Drag-and-drop reorder" do
|
||||||
|
test "reorders records via reorder event", %{conn: conn} do
|
||||||
|
{set, [r1, r2, r3]} = record_set_with_records(3)
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/record-sets")
|
||||||
|
|
||||||
|
render_hook(view, "reorder", %{
|
||||||
|
"set_id" => set.id,
|
||||||
|
"record_ids" => [r3.id, r1.id, r2.id]
|
||||||
|
})
|
||||||
|
|
||||||
|
updated = RecordSets.get_record_set!(set.id)
|
||||||
|
ids_in_order = Enum.map(updated.items, & &1.record.id)
|
||||||
|
assert ids_in_order == [r3.id, r1.id, r2.id]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "Reorder records" do
|
describe "Reorder records" do
|
||||||
test "moves a record left (up)", %{conn: conn} do
|
test "moves a record left (up)", %{conn: conn} do
|
||||||
{set, [r1, r2 | _]} = record_set_with_records(3)
|
{set, [r1, r2 | _]} = record_set_with_records(3)
|
||||||
|
|||||||
@@ -81,6 +81,20 @@ defmodule MusicLibraryWeb.RecordSetLive.ShowTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "Drag-and-drop reorder" do
|
||||||
|
test "reorders records via reorder event", %{conn: conn} do
|
||||||
|
{set, [r1, r2, r3]} = record_set_with_records(3)
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/record-sets/#{set}")
|
||||||
|
|
||||||
|
render_hook(view, "reorder", %{"record_ids" => [r3.id, r1.id, r2.id]})
|
||||||
|
|
||||||
|
updated = RecordSets.get_record_set!(set.id)
|
||||||
|
ids_in_order = Enum.map(updated.items, & &1.record.id)
|
||||||
|
assert ids_in_order == [r3.id, r1.id, r2.id]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "Delete set" do
|
describe "Delete set" do
|
||||||
test "deletes set and navigates to index", %{conn: conn} do
|
test "deletes set and navigates to index", %{conn: conn} do
|
||||||
set = record_set(%{name: "To Delete"})
|
set = record_set(%{name: "To Delete"})
|
||||||
|
|||||||
Reference in New Issue
Block a user