Просмотр исходного кода

refactor: Replaced draggable component with vue-draggable-list package

Owen Diffey 2 лет назад
Родитель
Сommit
a4186e4649

+ 17 - 11
frontend/package-lock.json

@@ -18,7 +18,6 @@
         "eslint-config-airbnb-base": "^15.0.0",
         "lofig": "^1.3.4",
         "marked": "^4.0.18",
-        "mobile-drag-drop": "^2.3.0-rc.2",
         "normalize.css": "^8.0.1",
         "pinia": "^2.0.17",
         "toasters": "^2.3.1",
@@ -27,6 +26,7 @@
         "vue": "^3.2.36",
         "vue-chartjs": "^4.1.1",
         "vue-content-loader": "^2.0.1",
+        "vue-draggable-list": "^0.1.0",
         "vue-json-pretty": "^2.1.1",
         "vue-router": "^4.1.3",
         "vue-tippy": "^6.0.0-alpha.63"
@@ -3396,11 +3396,6 @@
         "mkdirp": "bin/cmd.js"
       }
     },
-    "node_modules/mobile-drag-drop": {
-      "version": "2.3.0-rc.2",
-      "resolved": "https://registry.npmjs.org/mobile-drag-drop/-/mobile-drag-drop-2.3.0-rc.2.tgz",
-      "integrity": "sha512-4rHP0PUeWkSp0O3waNHPQZCHeZnLu8bE59MerWOnZJ249BCyICXL1WWp3xqkMKXEDFYuhfk3bS42bKB9IeN9uw=="
-    },
     "node_modules/ms": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -4494,6 +4489,14 @@
         "vue": "^3"
       }
     },
+    "node_modules/vue-draggable-list": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/vue-draggable-list/-/vue-draggable-list-0.1.0.tgz",
+      "integrity": "sha512-bSVxTaTqr16srBD7vpLLEbN+vAckDVTklWW3/iEfIr5GnBNZgSN+Z0Jnq75WnteZPdaT6BgfCdgISf6MzC5PSw==",
+      "dependencies": {
+        "vue": "^3.2.37"
+      }
+    },
     "node_modules/vue-eslint-parser": {
       "version": "9.0.3",
       "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.0.3.tgz",
@@ -7017,11 +7020,6 @@
         "minimist": "^1.2.6"
       }
     },
-    "mobile-drag-drop": {
-      "version": "2.3.0-rc.2",
-      "resolved": "https://registry.npmjs.org/mobile-drag-drop/-/mobile-drag-drop-2.3.0-rc.2.tgz",
-      "integrity": "sha512-4rHP0PUeWkSp0O3waNHPQZCHeZnLu8bE59MerWOnZJ249BCyICXL1WWp3xqkMKXEDFYuhfk3bS42bKB9IeN9uw=="
-    },
     "ms": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -7776,6 +7774,14 @@
       "integrity": "sha512-pkof4+q2xmzNEdhqelxtJejeP/vQUJtLle4/v2ueG+HURqM9Q/GIGC8GJ2bVVWeLfTDET51jqimwQdmxJTlu0g==",
       "requires": {}
     },
+    "vue-draggable-list": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/vue-draggable-list/-/vue-draggable-list-0.1.0.tgz",
+      "integrity": "sha512-bSVxTaTqr16srBD7vpLLEbN+vAckDVTklWW3/iEfIr5GnBNZgSN+Z0Jnq75WnteZPdaT6BgfCdgISf6MzC5PSw==",
+      "requires": {
+        "vue": "^3.2.37"
+      }
+    },
     "vue-eslint-parser": {
       "version": "9.0.3",
       "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.0.3.tgz",

+ 1 - 1
frontend/package.json

@@ -41,7 +41,6 @@
     "eslint-config-airbnb-base": "^15.0.0",
     "lofig": "^1.3.4",
     "marked": "^4.0.18",
-    "mobile-drag-drop": "^2.3.0-rc.2",
     "normalize.css": "^8.0.1",
     "pinia": "^2.0.17",
     "toasters": "^2.3.1",
@@ -50,6 +49,7 @@
     "vue": "^3.2.36",
     "vue-chartjs": "^4.1.1",
     "vue-content-loader": "^2.0.1",
+    "vue-draggable-list": "^0.1.0",
     "vue-json-pretty": "^2.1.1",
     "vue-router": "^4.1.3",
     "vue-tippy": "^6.0.0-alpha.63"

+ 1 - 0
frontend/src/App.vue

@@ -307,6 +307,7 @@ onMounted(async () => {
 @import "normalize.css/normalize.css";
 @import "tippy.js/dist/tippy.css";
 @import "tippy.js/animations/scale.css";
+@import "vue-draggable-list/dist/style.css";
 
 :root {
 	--primary-color: var(--blue);

+ 5 - 5
frontend/src/components/AdvancedTable.vue

@@ -13,10 +13,10 @@ import {
 import { useRoute, useRouter } from "vue-router";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
+import { DraggableList } from "vue-draggable-list";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import keyboardShortcuts from "@/keyboardShortcuts";
-import draggable from "@/components/Draggable.vue";
 import ws from "@/ws";
 import { useDragBox } from "@/composables/useDragBox";
 import {
@@ -1520,7 +1520,7 @@ watch(selectedRows, (newSelectedRows, oldSelectedRows) => {
 
 						<template #content>
 							<div class="nav-dropdown-items">
-								<draggable
+								<draggable-list
 									v-model:list="orderedColumns"
 									item-key="name"
 									@update="columnOrderChanged"
@@ -1590,7 +1590,7 @@ watch(selectedRows, (newSelectedRows, oldSelectedRows) => {
 											</div>
 										</template>
 									</template>
-								</draggable>
+								</draggable-list>
 							</div>
 						</template>
 					</tippy>
@@ -1605,7 +1605,7 @@ watch(selectedRows, (newSelectedRows, oldSelectedRows) => {
 				>
 					<thead>
 						<tr>
-							<draggable
+							<draggable-list
 								v-model:list="orderedColumns"
 								item-key="name"
 								@update="columnOrderChanged"
@@ -1724,7 +1724,7 @@ watch(selectedRows, (newSelectedRows, oldSelectedRows) => {
 										></div>
 									</template>
 								</template>
-							</draggable>
+							</draggable-list>
 						</tr>
 					</thead>
 					<tbody>

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

@@ -1,256 +0,0 @@
-<script setup lang="ts">
-/* eslint-disable import/first */
-import { PropType, Slot as SlotType, watch, onMounted, ref } from "vue";
-
-const props = defineProps({
-	itemKey: { type: String, default: "" },
-	list: { type: Array as PropType<any[]>, default: () => [] },
-	attributes: { type: Object, default: () => ({}) },
-	tag: { type: String, default: "div" },
-	class: { type: String, default: "" },
-	group: { type: String, default: "" },
-	disabled: { type: [Boolean, Function], default: false }
-});
-
-const listUuid = ref(
-	"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, symbol => {
-		let array;
-
-		if (symbol === "y") {
-			array = ["8", "9", "a", "b"];
-			return array[Math.floor(Math.random() * array.length)];
-		}
-
-		array = new Uint8Array(1);
-		window.crypto.getRandomValues(array);
-		return (array[0] % 16).toString(16);
-	})
-);
-const mounted = ref(false);
-const data = ref([] as any[]);
-
-watch(
-	() => props.list,
-	list => {
-		data.value = list;
-	}
-);
-
-onMounted(() => {
-	data.value = props.list;
-	mounted.value = true;
-});
-
-const emit = defineEmits(["update:list", "start", "end", "update"]);
-
-const itemOnMove = (index: number) => {
-	// Deletes the remove function for the dragging element
-	if (window.draggingItem && window.draggingItem.itemOnMove)
-		delete window.draggingItem.itemOnMove;
-	// Remove the item from the current list and return it
-	const listItem = data.value.splice(index, 1)[0];
-	emit("update:list", data.value);
-	return listItem;
-};
-
-// When an element starts being dragged
-const onDragStart = (itemIndex: number, event: DragEvent) => {
-	const { draggable } = event.target as HTMLElement;
-
-	if (props.disabled === true || !draggable || !event.dataTransfer) {
-		event.preventDefault();
-		return;
-	}
-
-	// 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 uuid and adds a remove function for when this item is moved to a different list
-	window.draggingItem = {
-		itemIndex,
-		itemListUuid: listUuid.value,
-		itemGroup: props.group,
-		itemOnMove,
-		initialItemIndex: itemIndex,
-		initialItemListUuid: listUuid.value
-	};
-
-	// 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: number, event: DragEvent, push = false) => {
-	const getDraggableElement = (element: HTMLElement): any =>
-		element.classList.contains("draggable-item") ||
-		element.classList.contains("empty-list-placeholder")
-			? element
-			: getDraggableElement(element.parentElement);
-	const draggableElement = getDraggableElement(event.target as HTMLElement);
-	const { draggable } = draggableElement;
-
-	if (
-		props.disabled === true ||
-		(!draggable && !push) ||
-		!window.draggingItem
-	)
-		return;
-
-	// The index and list uuid of the item that is being dragged, stored in window since it can come from another list as well
-	const fromIndex = window.draggingItem.itemIndex;
-	const fromList = window.draggingItem.itemListUuid;
-	// The new index and list uuid of the item that is being dragged
-	const toIndex = itemIndex;
-	const toList = listUuid.value;
-
-	// If the item hasn't changed position in the same list, don't continue
-	if (fromIndex === toIndex && fromList === toList) return;
-
-	// If the dragging item isn't from the same group, don't continue
-	if (
-		fromList !== toList &&
-		(props.group === "" || window.draggingItem.itemGroup !== props.group)
-	)
-		return;
-
-	// Update the index and list uuid of the dragged item
-	window.draggingItem.itemIndex = toIndex;
-	window.draggingItem.itemListUuid = listUuid.value;
-
-	// If the item comes from another list
-	if (toList !== fromList && window.draggingItem.itemOnMove) {
-		// Call the remove function from the dragging element, which removes the item from the previous list and returns it
-		const item = window.draggingItem.itemOnMove(fromIndex);
-		// Define a new remove function for the dragging element
-		window.draggingItem.itemOnMove = itemOnMove;
-		window.draggingItem.itemGroup = props.group;
-		// Add the item to the list at the new index
-		if (push) data.value.push(item);
-		else data.value.splice(toIndex, 0, item);
-		emit("update:list", data.value);
-	}
-	// 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
-		data.value.splice(toIndex, 0, data.value.splice(fromIndex, 1)[0]);
-		emit("update:list", data.value);
-	}
-};
-// 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
-	if (!window.draggingItem) return;
-	const { itemIndex, itemListUuid, initialItemIndex, initialItemListUuid } =
-		window.draggingItem;
-	if (itemListUuid === initialItemListUuid)
-		emit("update", {
-			moved: {
-				oldIndex: initialItemIndex,
-				newIndex: itemIndex,
-				updatedList: data.value
-			}
-		});
-	else emit("update", {});
-	delete window.draggingItem;
-};
-
-// Function that gets called for each item and returns attributes
-const convertAttributes = (item: any) =>
-	Object.fromEntries(
-		Object.entries(props.attributes).map(([key, value]) => [
-			key,
-			typeof value === "function" ? value(item) : value
-		])
-	);
-
-const hasSlotContent = (slot: SlotType | undefined, slotProps = {}) => {
-	if (!slot) return false;
-
-	return slot(slotProps).some(vnode => {
-		if (
-			vnode.type === Comment ||
-			vnode.type.toString() === "Symbol(Comment)"
-		)
-			return false;
-
-		if (Array.isArray(vnode.children) && !vnode.children.length)
-			return false;
-
-		return (
-			vnode.type !== Text ||
-			vnode.type.toString() !== "Symbol(Text)" ||
-			(typeof vnode.children === "string" && vnode.children.trim() !== "")
-		);
-	});
-};
-</script>
-
-<script lang="ts">
-import { polyfill as mobileDragDropPolyfill } from "mobile-drag-drop";
-import { scrollBehaviourDragImageTranslateOverride as mobileDragDropScrollBehaviourDragImageTranslateOverride } from "mobile-drag-drop/scroll-behaviour";
-
-mobileDragDropPolyfill({
-	dragImageTranslateOverride:
-		mobileDragDropScrollBehaviourDragImageTranslateOverride,
-	tryFindDraggableTarget: event => {
-		const getDraggableElement = (element: HTMLElement) => {
-			if (element.classList.contains("draggable-item")) return element;
-			if (element.parentElement)
-				return getDraggableElement(element.parentElement);
-			return undefined;
-		};
-
-		return getDraggableElement(event.target as HTMLElement);
-	}
-});
-
-window.addEventListener("touchmove", () => {});
-</script>
-
-<template>
-	<template v-for="(item, itemIndex) in data" :key="item[itemKey]">
-		<component
-			v-if="hasSlotContent($slots.item, { element: item })"
-			:is="tag"
-			:draggable="
-				typeof disabled === 'function' ? !disabled(item) : !disabled
-			"
-			@dragstart="onDragStart(itemIndex, $event)"
-			@dragenter.prevent
-			@dragover.prevent="onDragOver(itemIndex, $event)"
-			@dragend="onDragEnd()"
-			@drop.prevent="onDrop()"
-			:data-index="itemIndex"
-			:data-list="listUuid"
-			class="draggable-item"
-			v-bind="convertAttributes(item)"
-		>
-			<slot name="item" :element="item" :index="itemIndex"></slot>
-		</component>
-	</template>
-	<div
-		v-if="data.length === 0"
-		class="empty-list-placeholder"
-		@dragover.prevent="onDragOver(0, $event, true)"
-		@drop.prevent
-	></div>
-</template>
-
-<style scoped>
-@import "mobile-drag-drop/default.css";
-
-.draggable-item[draggable="true"] {
-	cursor: move;
-}
-.draggable-item:not(:last-of-type) {
-	margin-bottom: 10px;
-}
-.empty-list-placeholder {
-	flex: 1;
-}
-</style>

+ 3 - 3
frontend/src/components/PlaylistTabBase.vue

@@ -52,7 +52,7 @@ const featuredPlaylists = ref([]);
 const tabs = ref({});
 
 const {
-	Draggable,
+	DraggableList,
 	drag,
 	playlists,
 	savePlaylistOrder,
@@ -811,7 +811,7 @@ onMounted(() => {
 					class="menu-list scrollable-list"
 					v-if="playlists.length > 0"
 				>
-					<draggable
+					<draggable-list
 						v-model:list="playlists"
 						item-key="_id"
 						@start="drag = true"
@@ -967,7 +967,7 @@ onMounted(() => {
 								</template>
 							</playlist-item>
 						</template>
-					</draggable>
+					</draggable-list>
 				</div>
 
 				<p v-else class="has-text-centered scrollable-list">

+ 3 - 5
frontend/src/components/Queue.vue

@@ -2,6 +2,7 @@
 import { defineAsyncComponent, ref, computed, onUpdated } from "vue";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
+import { DraggableList } from "vue-draggable-list";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useStationStore } from "@/stores/station";
 import { useUserAuthStore } from "@/stores/userAuth";
@@ -13,9 +14,6 @@ const SongItem = defineAsyncComponent(
 const QuickConfirm = defineAsyncComponent(
 	() => import("@/components/QuickConfirm.vue")
 );
-const Draggable = defineAsyncComponent(
-	() => import("@/components/Draggable.vue")
-);
 
 const props = defineProps({
 	modalUuid: { type: String, default: "" },
@@ -144,7 +142,7 @@ onUpdated(() => {
 				'scrollable-list': true
 			}"
 		>
-			<draggable
+			<draggable-list
 				v-model:list="queue"
 				item-key="_id"
 				@start="drag = true"
@@ -194,7 +192,7 @@ onUpdated(() => {
 						</template>
 					</song-item>
 				</template>
-			</draggable>
+			</draggable-list>
 		</div>
 		<p class="nothing-here-text has-text-centered" v-else>
 			There are no songs currently queued

+ 3 - 5
frontend/src/components/modals/EditPlaylist/index.vue

@@ -8,6 +8,7 @@ import {
 } from "vue";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
+import { DraggableList } from "vue-draggable-list";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useEditPlaylistStore } from "@/stores/editPlaylist";
 import { useStationStore } from "@/stores/station";
@@ -28,9 +29,6 @@ const ImportPlaylists = defineAsyncComponent(
 const QuickConfirm = defineAsyncComponent(
 	() => import("@/components/QuickConfirm.vue")
 );
-const Draggable = defineAsyncComponent(
-	() => import("@/components/Draggable.vue")
-);
 
 const props = defineProps({
 	modalUuid: { type: String, default: "" }
@@ -407,7 +405,7 @@ onBeforeUnmount(() => {
 					</div>
 
 					<aside class="menu">
-						<draggable
+						<draggable-list
 							v-if="playlistSongs.length > 0"
 							v-model:list="playlistSongs"
 							item-key="_id"
@@ -491,7 +489,7 @@ onBeforeUnmount(() => {
 									</template>
 								</song-item>
 							</template>
-						</draggable>
+						</draggable-list>
 						<p v-else-if="gettingSongs" class="nothing-here-text">
 							Loading songs...
 						</p>

+ 5 - 7
frontend/src/components/modals/ImportAlbum.vue

@@ -8,6 +8,7 @@ import {
 } from "vue";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
+import { DraggableList } from "vue-draggable-list";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useImportAlbumStore } from "@/stores/importAlbum";
@@ -17,9 +18,6 @@ const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const SongItem = defineAsyncComponent(
 	() => import("@/components/SongItem.vue")
 );
-const Draggable = defineAsyncComponent(
-	() => import("@/components/Draggable.vue")
-);
 
 const props = defineProps({
 	modalUuid: { type: String, default: "" }
@@ -602,7 +600,7 @@ onBeforeUnmount(() => {
 					>
 						Reset
 					</button>
-					<draggable
+					<draggable-list
 						v-if="playlistSongs.length > 0"
 						v-model:list="playlistSongs"
 						item-key="_id"
@@ -615,7 +613,7 @@ onBeforeUnmount(() => {
 							>
 							</song-item>
 						</template>
-					</draggable>
+					</draggable-list>
 				</div>
 				<div
 					class="track-boxes"
@@ -632,7 +630,7 @@ onBeforeUnmount(() => {
 						</div>
 						<!-- :data-track-index="index" -->
 						<div class="track-box-songs-drag-area">
-							<draggable
+							<draggable-list
 								v-model:list="trackSongs[index]"
 								item-key="_id"
 								:group="`import-album-${modalUuid}-songs`"
@@ -644,7 +642,7 @@ onBeforeUnmount(() => {
 									>
 									</song-item>
 								</template>
-							</draggable>
+							</draggable-list>
 						</div>
 					</div>
 				</div>

+ 2 - 2
frontend/src/composables/useSortablePlaylists.ts

@@ -1,11 +1,11 @@
 import { ref, computed, onMounted, onBeforeUnmount, nextTick } from "vue";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
+import { DraggableList } from "vue-draggable-list";
 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([]);
@@ -182,7 +182,7 @@ export const useSortablePlaylists = () => {
 	});
 
 	return {
-		Draggable,
+		DraggableList,
 		drag,
 		userId,
 		isCurrentUser,

+ 3 - 5
frontend/src/pages/Home.vue

@@ -9,6 +9,7 @@ import {
 } from "vue";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
+import { DraggableList } from "vue-draggable-list";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useModalsStore } from "@/stores/modals";
@@ -27,9 +28,6 @@ const SongThumbnail = defineAsyncComponent(
 const UserLink = defineAsyncComponent(
 	() => import("@/components/UserLink.vue")
 );
-const Draggable = defineAsyncComponent(
-	() => import("@/components/Draggable.vue")
-);
 
 const userAuthStore = useUserAuthStore();
 const route = useRoute();
@@ -386,7 +384,7 @@ onBeforeUnmount(() => {
 					</div>
 				</div>
 
-				<draggable
+				<draggable-list
 					item-key="_id"
 					tag="span"
 					:list="favoriteStations"
@@ -604,7 +602,7 @@ onBeforeUnmount(() => {
 							</div>
 						</router-link>
 					</template>
-				</draggable>
+				</draggable-list>
 			</div>
 			<div class="group bottom">
 				<div class="group-title">

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

@@ -12,8 +12,14 @@ const props = defineProps({
 	username: { type: String, default: "" }
 });
 
-const { Draggable, drag, userId, isCurrentUser, playlists, savePlaylistOrder } =
-	useSortablePlaylists();
+const {
+	DraggableList,
+	drag,
+	userId,
+	isCurrentUser,
+	playlists,
+	savePlaylistOrder
+} = useSortablePlaylists();
 
 const { openModal } = useModalsStore();
 
@@ -40,7 +46,7 @@ onMounted(() => {
 
 			<hr class="section-horizontal-rule" />
 
-			<draggable
+			<draggable-list
 				v-if="playlists.length > 0"
 				v-model:list="playlists"
 				item-key="_id"
@@ -87,7 +93,7 @@ onMounted(() => {
 						</template>
 					</playlist-item>
 				</template>
-			</draggable>
+			</draggable-list>
 
 			<button
 				v-if="isCurrentUser"