Browse Source

refactor(Queue): Implemented custom draggable component

Owen Diffey 2 years ago
parent
commit
8e94193225

+ 6 - 3
frontend/src/components/Draggable.vue

@@ -34,7 +34,7 @@ const itemOnMove = index => {
 
 // When an element starts being dragged
 const onDragStart = (itemIndex: number, event: DragEvent) => {
-	const { draggable } = event.target;
+	const { draggable } = event.target as HTMLElement;
 
 	if (props.disabled === true || !draggable) {
 		event.preventDefault();
@@ -76,6 +76,9 @@ const onDragOver = (itemIndex: number, event: DragEvent) => {
 	const toIndex = itemIndex;
 	const toList = props.name;
 
+	// Don't continue if fromIndex is invalid
+	if (fromIndex === -1 || toIndex === -1) return;
+
 	// If the item hasn't changed position in the same list, don't continue
 	if (fromIndex === toIndex && fromList === toList) return;
 
@@ -125,7 +128,7 @@ const onDrop = () => {
 };
 
 // Function that gets called for each item and returns attributes
-const convertAttributes = item =>
+const convertAttributes = (item: any) =>
 	Object.fromEntries(
 		Object.entries(props.attributes).map(([key, value]) => [
 			key,
@@ -173,7 +176,7 @@ const hasSlotContent = (slot, slotProps = {}) => {
 			class="draggable-item"
 			v-bind="convertAttributes(item)"
 		>
-			<slot name="item" :element="item"></slot>
+			<slot name="item" :element="item" :index="itemIndex"></slot>
 		</component>
 	</template>
 </template>

+ 30 - 26
frontend/src/components/Queue.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
 import { defineAsyncComponent, ref, computed, onUpdated } from "vue";
-import { Sortable } from "sortablejs-vue3";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
@@ -14,6 +13,9 @@ const SongItem = defineAsyncComponent(
 const QuickConfirm = defineAsyncComponent(
 	() => import("@/components/QuickConfirm.vue")
 );
+const Draggable = defineAsyncComponent(
+	() => import("@/components/Draggable.vue")
+);
 
 const props = defineProps({
 	modalUuid: { type: String, default: "" },
@@ -27,12 +29,6 @@ const manageStationStore = useManageStationStore(props);
 
 const { loggedIn, userId, role: userRole } = storeToRefs(userAuthStore);
 
-const repositionSongInList = payload => {
-	if (props.sector === "manageStation")
-		return manageStationStore.repositionSongInList(payload);
-	return stationStore.repositionSongInList(payload);
-};
-
 const actionableButtonVisible = ref(false);
 const drag = ref(false);
 const songItems = ref([]);
@@ -87,9 +83,10 @@ const removeFromQueue = youtubeId => {
 	);
 };
 
-const repositionSongInQueue = ({ oldIndex, newIndex }) => {
+const repositionSongInQueue = ({ moved }) => {
+	const { oldIndex, newIndex } = moved;
 	if (oldIndex === newIndex) return; // we only need to update when song is moved
-	const song = queue.value[oldIndex];
+	const song = queue.value[newIndex];
 	socket.dispatch(
 		"stations.repositionSongInQueue",
 		station.value._id,
@@ -101,30 +98,38 @@ const repositionSongInQueue = ({ oldIndex, newIndex }) => {
 		res => {
 			new Toast({ content: res.message, timeout: 4000 });
 			if (res.status !== "success")
-				repositionSongInList({
-					...song,
+				queue.value.splice(
 					oldIndex,
-					newIndex
-				});
+					0,
+					queue.value.splice(newIndex, 1)[0]
+				);
 		}
 	);
 };
 
 const moveSongToTop = index => {
 	songItems.value[`song-item-${index}`].$refs.songActions.tippy.hide();
-
+	queue.value.splice(0, 0, queue.value.splice(index, 1)[0]);
 	repositionSongInQueue({
-		oldIndex: index,
-		newIndex: 0
+		moved: {
+			oldIndex: index,
+			newIndex: 0
+		}
 	});
 };
 
 const moveSongToBottom = index => {
 	songItems.value[`song-item-${index}`].$refs.songActions.tippy.hide();
-
+	queue.value.splice(
+		queue.value.length - 1,
+		0,
+		queue.value.splice(index, 1)[0]
+	);
 	repositionSongInQueue({
-		oldIndex: index,
-		newIndex: queue.value.length
+		moved: {
+			oldIndex: index,
+			newIndex: queue.value.length - 1
+		}
 	});
 };
 
@@ -146,24 +151,23 @@ onUpdated(() => {
 				'scrollable-list': true
 			}"
 		>
-			<Sortable
+			<draggable
 				:component-data="{
 					name: !drag ? 'draggable-list-transition' : null
 				}"
-				:list="queue"
+				:name="`queue-${modalUuid}-${sector}`"
+				v-model:list="queue"
 				item-key="_id"
 				:options="dragOptions"
 				@start="drag = true"
 				@end="drag = false"
 				@update="repositionSongInQueue"
+				:disabled="!(isAdminOnly() || isOwnerOnly())"
 			>
 				<template #item="{ element, index }">
 					<song-item
 						:song="element"
 						:requested-by="true"
-						:class="{
-							'item-draggable': isAdminOnly() || isOwnerOnly()
-						}"
 						:disabled-actions="[]"
 						:ref="el => (songItems[`song-item-${index}`] = el)"
 					>
@@ -202,7 +206,7 @@ onUpdated(() => {
 						</template>
 					</song-item>
 				</template>
-			</Sortable>
+			</draggable>
 		</div>
 		<p class="nothing-here-text has-text-centered" v-else>
 			There are no songs currently queued
@@ -227,7 +231,7 @@ onUpdated(() => {
 		max-height: 100%;
 	}
 
-	.song-item:not(:last-of-type) {
+	:deep(.draggable-item:not(:last-of-type)) {
 		margin-bottom: 10px;
 	}
 

+ 10 - 3
frontend/src/types/global.d.ts

@@ -10,9 +10,16 @@ declare global {
 	var addToPlaylistDropdown: any;
 	var scrollDebounceId: any;
 	var focusedElementBefore: any;
-	var draggingItemIndex: undefined | number;
-	var draggingItemListName: undefined | string;
-	var draggingItemOnMove: undefined | ((index: number) => any);
+	var draggingItem:
+		| undefined
+		| {
+				itemIndex: number;
+				itemListName: string;
+				itemGroup: string;
+				itemOnMove: (index: number) => any;
+				initialItemIndex: number;
+				initialItemListName: string;
+		  };
 }
 
 export {};