Browse Source

feat: added custom draggable component, mostly migrated profile playlists tab (WIP)

Kristian Vos 2 years ago
parent
commit
f36734b2c3

+ 111 - 0
frontend/src/components/Draggable.vue

@@ -0,0 +1,111 @@
+<script setup lang="ts">
+/* eslint-disable vue/no-mutating-props */
+import { onMounted, ref } from "vue";
+
+const props = defineProps({
+	name: { type: String, default: "" },
+	itemKey: { type: String, default: "" },
+	list: { type: Array, default: () => [] },
+	componentData: { type: Object, default: () => ({}) },
+	options: { type: Object, default: () => ({}) }
+});
+
+const mounted = ref(false);
+
+onMounted(() => {
+	mounted.value = true;
+});
+
+const emit = defineEmits(["start", "end", "update"]);
+
+// When an element starts being dragged
+const onDragStart = (itemIndex, event) => {
+	// Set the effect of moving an element, which by default is clone. Not being used right now
+	event.dataTransfer.dropEffect = "move";
+
+	// Sets the dragging element index, list name and adds a remove function for when this item is moved to a different list
+	window.draggingItemIndex = itemIndex;
+	window.draggingItemListName = props.name;
+	window.draggingItemOnMove = index => {
+		window.draggingItemOnMove = null;
+		return props.list.splice(index, 1)[0];
+	};
+
+	// Emits the start event to the parent component, indicating that dragging has started
+	emit("start");
+};
+
+// When a dragging element hovers over another draggable element, this gets triggered, usually many times in a second
+const onDragOver = itemIndex => {
+	// The index and list name of the item that is being dragged, stored in window since it can come from another list as well
+	const fromIndex = window.draggingItemIndex;
+	const fromList = window.draggingItemList;
+	// The new index and list name of the item that is being dragged
+	const toIndex = itemIndex;
+	const toList = props.name;
+
+	// If the item hasn't changed position in the same list, don't continue
+	if (fromIndex === toIndex && fromList === toList) return;
+
+	// Update the index and list name of the dragged item
+	window.draggingItemIndex = toIndex;
+	window.draggingItemList = props.name;
+
+	// If the item comes from another list
+	if (toList !== fromList) {
+		// Call the remove function from the dragging element, which removes the item from the previous list and returns it
+		const item = window.draggingItemOnMove(fromIndex);
+		// Define a new remove function for the dragging element
+		window.draggingItemOnMove = index => {
+			// Deletes the remove function for the dragging element
+			window.draggingItemOnMove = null;
+			// Remove the item from the current list and return it
+			return props.list.splice(index, 1)[0];
+		};
+		// Add the item to the list at the new index
+		props.list.splice(toIndex, 0, item);
+	}
+	// If the item is being reordered in the same list
+	else {
+		// Remove the item from the old position, and add the item to the new position
+		props.list.splice(toIndex, 0, props.list.splice(fromIndex, 1)[0]);
+	}
+};
+// Gets called when the element that is being dragged is released
+const onDragEnd = () => {
+	// Emits the end event to parent component, indicating that dragging has ended
+	emit("end");
+};
+// Gets called when an element is dropped on another element
+const onDrop = () => {
+	// Emits the update event to parent component, indicating that the order is now done and ordering/moving is done
+	emit("update");
+};
+</script>
+
+<template>
+	<div
+		v-for="n in list.length"
+		:key="`${name}-${n - 1}`"
+		:id="`${name}-${n - 1}`"
+	></div>
+
+	<template v-if="mounted">
+		<div v-for="(item, itemIndex) in list" :key="item[itemKey]">
+			<Teleport :to="`#${name}-${itemIndex}`">
+				<div
+					draggable="true"
+					@dragstart="onDragStart(itemIndex, $event)"
+					@dragenter.prevent
+					@dragover.prevent="onDragOver(itemIndex)"
+					@dragend="onDragEnd()"
+					@drop.prevent="onDrop()"
+					:data-index="itemIndex"
+					:data-list="name"
+				>
+					<slot name="item" :element="item"></slot>
+				</div>
+			</Teleport>
+		</div>
+	</template>
+</template>

+ 8 - 10
frontend/src/composables/useSortablePlaylists.ts

@@ -1,11 +1,11 @@
 import { ref, computed, onMounted, onBeforeUnmount, nextTick } from "vue";
-import { Sortable } from "sortablejs-vue3";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useUserPlaylistsStore } from "@/stores/userPlaylists";
 import ws from "@/ws";
+import Draggable from "@/components/Draggable.vue";
 
 export const useSortablePlaylists = () => {
 	const orderOfPlaylists = ref([]);
@@ -42,15 +42,13 @@ export const useSortablePlaylists = () => {
 		return calculatedOrder;
 	};
 
-	const savePlaylistOrder = ({ oldIndex, newIndex }) => {
-		if (oldIndex === newIndex) return;
-		const oldPlaylists = playlists.value;
-
-		oldPlaylists.splice(newIndex, 0, oldPlaylists.splice(oldIndex, 1)[0]);
-
-		setPlaylists(oldPlaylists);
-
+	const savePlaylistOrder = () => {
 		const recalculatedOrder = calculatePlaylistOrder();
+		if (
+			JSON.stringify(orderOfPlaylists.value) ===
+			JSON.stringify(recalculatedOrder)
+		)
+			return; // nothing has changed
 
 		socket.dispatch(
 			"users.updateOrderOfPlaylists",
@@ -190,7 +188,7 @@ export const useSortablePlaylists = () => {
 	});
 
 	return {
-		Sortable,
+		Draggable,
 		drag,
 		userId,
 		isCurrentUser,

+ 4 - 3
frontend/src/pages/Profile/Tabs/Playlists.vue

@@ -13,7 +13,7 @@ const props = defineProps({
 });
 
 const {
-	Sortable,
+	Draggable,
 	drag,
 	userId,
 	isCurrentUser,
@@ -47,10 +47,11 @@ onMounted(() => {
 
 			<hr class="section-horizontal-rule" />
 
-			<sortable
+			<draggable
 				:component-data="{
 					name: !drag ? 'draggable-list-transition' : null
 				}"
+				name="profile-playlists"
 				v-if="playlists.length > 0"
 				:list="playlists"
 				item-key="_id"
@@ -102,7 +103,7 @@ onMounted(() => {
 						</template>
 					</playlist-item>
 				</template>
-			</sortable>
+			</draggable>
 
 			<button
 				v-if="isCurrentUser"