Browse Source

refactor(Draggable): Use v-models for prop mutation and other tweaks

Owen Diffey 2 years ago
parent
commit
8b7f59b04c

+ 37 - 37
frontend/src/components/Draggable.vue

@@ -1,11 +1,10 @@
 <script setup lang="ts">
-/* eslint-disable vue/no-mutating-props */
-import { onMounted, ref } from "vue";
+import { PropType, onMounted, ref } from "vue";
 
 const props = defineProps({
 	name: { type: String, default: "" },
 	itemKey: { type: String, default: "" },
-	list: { type: Array, default: () => [] },
+	list: { type: Array as PropType<any[]>, default: () => [] },
 	componentData: { type: Object, default: () => ({}) },
 	options: { type: Object, default: () => ({}) }
 });
@@ -16,10 +15,10 @@ onMounted(() => {
 	mounted.value = true;
 });
 
-const emit = defineEmits(["start", "end", "update"]);
+const emit = defineEmits(["update:list", "start", "end", "update"]);
 
 // When an element starts being dragged
-const onDragStart = (itemIndex, event) => {
+const onDragStart = (itemIndex: number, event: DragEvent) => {
 	// Set the effect of moving an element, which by default is clone. Not being used right now
 	event.dataTransfer.dropEffect = "move";
 
@@ -27,8 +26,11 @@ const onDragStart = (itemIndex, event) => {
 	window.draggingItemIndex = itemIndex;
 	window.draggingItemListName = props.name;
 	window.draggingItemOnMove = index => {
-		window.draggingItemOnMove = null;
-		return props.list.splice(index, 1)[0];
+		delete window.draggingItemOnMove;
+		const list = props.list.slice();
+		const listItem = list.splice(index, 1)[0];
+		emit("update:list", list);
+		return listItem;
 	};
 
 	// Emits the start event to the parent component, indicating that dragging has started
@@ -36,10 +38,10 @@ const onDragStart = (itemIndex, event) => {
 };
 
 // When a dragging element hovers over another draggable element, this gets triggered, usually many times in a second
-const onDragOver = itemIndex => {
+const onDragOver = (itemIndex: number) => {
 	// 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;
+	const fromList = window.draggingItemListName;
 	// The new index and list name of the item that is being dragged
 	const toIndex = itemIndex;
 	const toList = props.name;
@@ -49,7 +51,7 @@ const onDragOver = itemIndex => {
 
 	// Update the index and list name of the dragged item
 	window.draggingItemIndex = toIndex;
-	window.draggingItemList = props.name;
+	window.draggingItemListName = props.name;
 
 	// If the item comes from another list
 	if (toList !== fromList) {
@@ -58,17 +60,24 @@ const onDragOver = itemIndex => {
 		// Define a new remove function for the dragging element
 		window.draggingItemOnMove = index => {
 			// Deletes the remove function for the dragging element
-			window.draggingItemOnMove = null;
+			delete window.draggingItemOnMove;
 			// Remove the item from the current list and return it
-			return props.list.splice(index, 1)[0];
+			const list = props.list.slice();
+			const listItem = list.splice(index, 1)[0];
+			emit("update:list", list);
+			return listItem;
 		};
 		// Add the item to the list at the new index
-		props.list.splice(toIndex, 0, item);
+		const list = props.list.slice();
+		list.splice(toIndex, 0, item);
+		emit("update:list", list);
 	}
 	// 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]);
+		const list = props.list.slice();
+		list.splice(toIndex, 0, list.splice(fromIndex, 1)[0]);
+		emit("update:list", list);
 	}
 };
 // Gets called when the element that is being dragged is released
@@ -85,27 +94,18 @@ const onDrop = () => {
 
 <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>
+		v-for="(item, itemIndex) in list"
+		:key="item[itemKey]"
+		class="draggable-item"
+		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>
 </template>

+ 6 - 5
frontend/src/components/PlaylistTabBase.vue

@@ -52,7 +52,7 @@ const featuredPlaylists = ref([]);
 const tabs = ref({});
 
 const {
-	Sortable,
+	Draggable,
 	drag,
 	playlists,
 	dragOptions,
@@ -812,12 +812,13 @@ onMounted(() => {
 					class="menu-list scrollable-list"
 					v-if="playlists.length > 0"
 				>
-					<sortable
+					<draggable
 						:component-data="{
 							name: !drag ? 'draggable-list-transition' : null
 						}"
+						:name="`my-playlists-${type}-${sector}-${modalUuid}`"
+						v-model:list="playlists"
 						item-key="_id"
-						:list="playlists"
 						:options="dragOptions"
 						@start="drag = true"
 						@end="drag = false"
@@ -975,7 +976,7 @@ onMounted(() => {
 								</template>
 							</playlist-item>
 						</template>
-					</sortable>
+					</draggable>
 				</div>
 
 				<p v-else class="has-text-centered scrollable-list">
@@ -1033,7 +1034,7 @@ onMounted(() => {
 			padding: 15px 0;
 			border-radius: 0;
 			.playlist-item:not(:last-of-type),
-			.item.item-draggable:not(:last-of-type) {
+			:deep(.draggable-item:not(:last-of-type)) {
 				margin-bottom: 10px;
 			}
 			.load-more-button {

+ 1 - 2
frontend/src/pages/Profile/Tabs/Playlists.vue

@@ -53,7 +53,7 @@ onMounted(() => {
 				}"
 				name="profile-playlists"
 				v-if="playlists.length > 0"
-				:list="playlists"
+				v-model:list="playlists"
 				item-key="_id"
 				:options="dragOptions"
 				@start="drag = true"
@@ -69,7 +69,6 @@ onMounted(() => {
 						"
 						:playlist="element"
 						:class="{
-							item: true,
 							'item-draggable': isCurrentUser
 						}"
 					>

+ 2 - 1
frontend/src/pages/Profile/index.vue

@@ -394,7 +394,8 @@ onMounted(() => {
 			font-weight: 400;
 		}
 
-		.item {
+		.item,
+		.draggable-item {
 			overflow: hidden;
 
 			&:not(:last-of-type) {

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

@@ -10,6 +10,9 @@ 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);
 }
 
 export {};