Преглед изворни кода

refactor: combined EditSong and EditSongs modal

Kristian Vos пре 2 година
родитељ
комит
763f2ff8d5

+ 1 - 1
backend/logic/actions/songs.js

@@ -289,7 +289,7 @@ export default {
 
 
 	/**
 	/**
 	 * Gets multiple songs from the Musare song ids
 	 * Gets multiple songs from the Musare song ids
-	 * At this time only used in EditSongs
+	 * At this time only used in bulk EditSong
 	 *
 	 *
 	 * @param {object} session - the session object automatically added by the websocket
 	 * @param {object} session - the session object automatically added by the websocket
 	 * @param {Array} youtubeIds - the song ids
 	 * @param {Array} youtubeIds - the song ids

+ 1 - 4
frontend/src/App.vue

@@ -166,10 +166,7 @@ onMounted(async () => {
 				Object.keys(activeModals.value).length !== 0 &&
 				Object.keys(activeModals.value).length !== 0 &&
 				modals.value[
 				modals.value[
 					activeModals.value[activeModals.value.length - 1]
 					activeModals.value[activeModals.value.length - 1]
-				] !== "editSong" &&
-				modals.value[
-					activeModals.value[activeModals.value.length - 1]
-				] !== "editSongs"
+				] !== "editSong"
 			)
 			)
 				closeCurrentModal();
 				closeCurrentModal();
 		}
 		}

+ 0 - 1
frontend/src/components/ModalManager.vue

@@ -25,7 +25,6 @@ const modalComponents = shallowRef(
 		removeAccount: "RemoveAccount.vue",
 		removeAccount: "RemoveAccount.vue",
 		importAlbum: "ImportAlbum.vue",
 		importAlbum: "ImportAlbum.vue",
 		confirm: "Confirm.vue",
 		confirm: "Confirm.vue",
-		editSongs: "EditSongs.vue",
 		editSong: "EditSong/index.vue",
 		editSong: "EditSong/index.vue",
 		viewYoutubeVideo: "ViewYoutubeVideo.vue"
 		viewYoutubeVideo: "ViewYoutubeVideo.vue"
 	})
 	})

+ 644 - 54
frontend/src/components/modals/EditSong/index.vue

@@ -14,6 +14,8 @@ import ws from "@/ws";
 import validation from "@/validation";
 import validation from "@/validation";
 import keyboardShortcuts from "@/keyboardShortcuts";
 import keyboardShortcuts from "@/keyboardShortcuts";
 
 
+import { Song } from "@/types/song.js";
+
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 import { useEditSongStore } from "@/stores/editSong";
 import { useEditSongStore } from "@/stores/editSong";
@@ -27,6 +29,9 @@ const SaveButton = defineAsyncComponent(
 const AutoSuggest = defineAsyncComponent(
 const AutoSuggest = defineAsyncComponent(
 	() => import("@/components/AutoSuggest.vue")
 	() => import("@/components/AutoSuggest.vue")
 );
 );
+const SongItem = defineAsyncComponent(
+	() => import("@/components/SongItem.vue")
+);
 const Discogs = defineAsyncComponent(() => import("./Tabs/Discogs.vue"));
 const Discogs = defineAsyncComponent(() => import("./Tabs/Discogs.vue"));
 const ReportsTab = defineAsyncComponent(() => import("./Tabs/Reports.vue"));
 const ReportsTab = defineAsyncComponent(() => import("./Tabs/Reports.vue"));
 const Youtube = defineAsyncComponent(() => import("./Tabs/Youtube.vue"));
 const Youtube = defineAsyncComponent(() => import("./Tabs/Youtube.vue"));
@@ -38,19 +43,14 @@ const props = defineProps({
 		type: String,
 		type: String,
 		default: "modals/editSong/MODAL_UUID"
 		default: "modals/editSong/MODAL_UUID"
 	},
 	},
-	discogsAlbum: { type: Object, default: null },
-	bulk: { type: Boolean, default: false },
-	flagged: { type: Boolean, default: false }
+	discogsAlbum: { type: Object, default: null }
 });
 });
 
 
 const emit = defineEmits([
 const emit = defineEmits([
 	"error",
 	"error",
-	"savedSuccess",
-	"savedError",
 	"flagSong",
 	"flagSong",
 	"nextSong",
 	"nextSong",
 	"close",
 	"close",
-	"saving",
 	"toggleFlag"
 	"toggleFlag"
 ]);
 ]);
 
 
@@ -69,7 +69,10 @@ const {
 	prefillData,
 	prefillData,
 	originalSong,
 	originalSong,
 	reports,
 	reports,
-	newSong
+	newSong,
+	bulk,
+	youtubeIds,
+	songPrefillData
 } = storeToRefs(editSongStore);
 } = storeToRefs(editSongStore);
 
 
 const songDataLoaded = ref(false);
 const songDataLoaded = ref(false);
@@ -141,6 +144,13 @@ const interval = ref();
 const saveButtonRefs = ref(<any>[]);
 const saveButtonRefs = ref(<any>[]);
 const canvasElement = ref();
 const canvasElement = ref();
 const genreHelper = ref();
 const genreHelper = ref();
+// EditSongs
+const items = ref([]);
+const currentSong = ref(<Song>{});
+const flagFilter = ref(false);
+const sidebarMobileActive = ref(false);
+const songItems = ref([]);
+// EditSongs end
 
 
 const isYoutubeThumbnail = computed(
 const isYoutubeThumbnail = computed(
 	() =>
 	() =>
@@ -150,8 +160,37 @@ const isYoutubeThumbnail = computed(
 		(song.value.thumbnail.lastIndexOf("i.ytimg.com") !== -1 ||
 		(song.value.thumbnail.lastIndexOf("i.ytimg.com") !== -1 ||
 			song.value.thumbnail.lastIndexOf("img.youtube.com") !== -1)
 			song.value.thumbnail.lastIndexOf("img.youtube.com") !== -1)
 );
 );
+// EditSongs
+const editingItemIndex = computed(() =>
+	items.value.findIndex(
+		item => item.song.youtubeId === currentSong.value.youtubeId
+	)
+);
+const filteredItems = computed({
+	get: () =>
+		items.value.filter(item => (flagFilter.value ? item.flagged : true)),
+	set: (newItem: any) => {
+		const index = items.value.findIndex(
+			item => item.song.youtubeId === newItem.youtubeId
+		);
+		items.value[index] = newItem;
+	}
+});
+const filteredEditingItemIndex = computed(() =>
+	filteredItems.value.findIndex(
+		item => item.song.youtubeId === currentSong.value.youtubeId
+	)
+);
+const currentSongFlagged = computed(
+	() =>
+		items.value.find(
+			item => item.song.youtubeId === currentSong.value.youtubeId
+		)?.flagged
+);
+// EditSongs end
 
 
 const {
 const {
+	editSong,
 	stopVideo,
 	stopVideo,
 	hardStopVideo,
 	hardStopVideo,
 	loadVideoById,
 	loadVideoById,
@@ -164,17 +203,145 @@ const {
 	setPlaybackRate
 	setPlaybackRate
 } = editSongStore;
 } = editSongStore;
 
 
-const closeCurrentModal = () => {
-	if (props.bulk) emit("close");
-	else modalsStore.closeCurrentModal();
-};
-
 const showTab = payload => {
 const showTab = payload => {
 	if (tabs.value[`${payload}-tab`])
 	if (tabs.value[`${payload}-tab`])
 		tabs.value[`${payload}-tab`].scrollIntoView({ block: "nearest" });
 		tabs.value[`${payload}-tab`].scrollIntoView({ block: "nearest" });
 	editSongStore.showTab(payload);
 	editSongStore.showTab(payload);
 };
 };
 
 
+// EditSongs
+const toggleDone = (index, overwrite = null) => {
+	const { status } = filteredItems.value[index];
+
+	if (status === "done" && overwrite !== "done")
+		filteredItems.value[index].status = "todo";
+	else {
+		filteredItems.value[index].status = "done";
+		filteredItems.value[index].flagged = false;
+	}
+};
+
+const toggleFlagFilter = () => {
+	flagFilter.value = !flagFilter.value;
+};
+
+const toggleMobileSidebar = () => {
+	sidebarMobileActive.value = !sidebarMobileActive.value;
+};
+
+const pickSong = song => {
+	editSong({
+		youtubeId: song.youtubeId,
+		prefill: songPrefillData.value[song.youtubeId]
+	});
+	currentSong.value = song;
+	if (
+		songItems.value[`edit-songs-item-${song.youtubeId}`] &&
+		songItems.value[`edit-songs-item-${song.youtubeId}`][0]
+	)
+		songItems.value[
+			`edit-songs-item-${song.youtubeId}`
+		][0].scrollIntoView();
+};
+
+const editNextSong = () => {
+	const currentlyEditingSongIndex = filteredEditingItemIndex.value;
+	let newEditingSongIndex = -1;
+	const index =
+		currentlyEditingSongIndex + 1 === filteredItems.value.length
+			? 0
+			: currentlyEditingSongIndex + 1;
+	for (let i = index; i < filteredItems.value.length; i += 1) {
+		if (!flagFilter.value || filteredItems.value[i].flagged) {
+			newEditingSongIndex = i;
+			break;
+		}
+	}
+
+	if (newEditingSongIndex > -1) {
+		const nextSong = filteredItems.value[newEditingSongIndex].song;
+		if (nextSong.removed) editNextSong();
+		else pickSong(nextSong);
+	}
+};
+
+const toggleFlag = (songIndex = null) => {
+	if (songIndex && songIndex > -1) {
+		filteredItems.value[songIndex].flagged =
+			!filteredItems.value[songIndex].flagged;
+		new Toast(
+			`Successfully ${
+				filteredItems.value[songIndex].flagged ? "flagged" : "unflagged"
+			} song.`
+		);
+	} else if (!songIndex && editingItemIndex.value > -1) {
+		items.value[editingItemIndex.value].flagged =
+			!items.value[editingItemIndex.value].flagged;
+		new Toast(
+			`Successfully ${
+				items.value[editingItemIndex.value].flagged
+					? "flagged"
+					: "unflagged"
+			} song.`
+		);
+	}
+};
+
+const onSavedSuccess = youtubeId => {
+	const itemIndex = items.value.findIndex(
+		item => item.song.youtubeId === youtubeId
+	);
+	if (itemIndex > -1) {
+		items.value[itemIndex].status = "done";
+		items.value[itemIndex].flagged = false;
+	}
+};
+
+const onSavedError = youtubeId => {
+	const itemIndex = items.value.findIndex(
+		item => item.song.youtubeId === youtubeId
+	);
+	if (itemIndex > -1) items.value[itemIndex].status = "error";
+};
+
+const onSaving = youtubeId => {
+	const itemIndex = items.value.findIndex(
+		item => item.song.youtubeId === youtubeId
+	);
+	if (itemIndex > -1) items.value[itemIndex].status = "saving";
+};
+
+const closeCurrentModal = () => {
+	if (bulk.value) {
+		const doneItems = items.value.filter(
+			item => item.status === "done"
+		).length;
+		const flaggedItems = items.value.filter(item => item.flagged).length;
+		const notDoneItems = items.value.length - doneItems;
+
+		if (doneItems > 0 && notDoneItems > 0)
+			openModal({
+				modal: "confirm",
+				data: {
+					message:
+						"You have songs which are not done yet. Are you sure you want to stop editing songs?",
+					onCompleted: modalsStore.closeCurrentModal
+				}
+			});
+		else if (flaggedItems > 0)
+			openModal({
+				modal: "confirm",
+				data: {
+					message:
+						"You have songs which are flagged. Are you sure you want to stop editing songs?",
+					onCompleted: modalsStore.closeCurrentModal
+				}
+			});
+		else modalsStore.closeCurrentModal();
+	} else modalsStore.closeCurrentModal();
+};
+// EditSongs end
+
 const onThumbnailLoad = () => {
 const onThumbnailLoad = () => {
 	if (thumbnailElement.value) {
 	if (thumbnailElement.value) {
 		const height = thumbnailElement.value.naturalHeight;
 		const height = thumbnailElement.value.naturalHeight;
@@ -243,8 +410,8 @@ const loadSong = _youtubeId => {
 			}
 			}
 		} else {
 		} else {
 			new Toast("Song with that ID not found");
 			new Toast("Song with that ID not found");
-			if (props.bulk) songNotFound.value = true;
-			if (!props.bulk) closeCurrentModal();
+			if (bulk.value) songNotFound.value = true;
+			if (!bulk.value) closeCurrentModal();
 		}
 		}
 	});
 	});
 };
 };
@@ -294,7 +461,7 @@ const seekTo = position => {
 };
 };
 
 
 const init = () => {
 const init = () => {
-	if (newSong.value && !youtubeId.value && !props.bulk) {
+	if (newSong.value && !youtubeId.value && !bulk.value) {
 		setSong({
 		setSong({
 			youtubeId: "",
 			youtubeId: "",
 			title: "",
 			title: "",
@@ -309,7 +476,7 @@ const init = () => {
 		songDataLoaded.value = true;
 		songDataLoaded.value = true;
 		showTab("youtube");
 		showTab("youtube");
 	} else if (youtubeId.value) loadSong(youtubeId.value);
 	} else if (youtubeId.value) loadSong(youtubeId.value);
-	else if (!props.bulk) {
+	else if (!bulk.value) {
 		new Toast("You can't open EditSong without editing a song");
 		new Toast("You can't open EditSong without editing a song");
 		return closeCurrentModal();
 		return closeCurrentModal();
 	}
 	}
@@ -521,25 +688,25 @@ const init = () => {
 const save = (songToCopy, closeOrNext, saveButtonRefName, _newSong = false) => {
 const save = (songToCopy, closeOrNext, saveButtonRefName, _newSong = false) => {
 	const _song = JSON.parse(JSON.stringify(songToCopy));
 	const _song = JSON.parse(JSON.stringify(songToCopy));
 
 
-	if (!newSong.value || props.bulk) emit("saving", _song.youtubeId);
+	if (!newSong.value || bulk.value) onSaving(_song.youtubeId);
 
 
 	const saveButtonRef = saveButtonRefs.value[saveButtonRefName];
 	const saveButtonRef = saveButtonRefs.value[saveButtonRefName];
 
 
 	if (!youtubeError.value && youtubeVideoDuration.value === "0.000") {
 	if (!youtubeError.value && youtubeVideoDuration.value === "0.000") {
 		saveButtonRef.handleFailedSave();
 		saveButtonRef.handleFailedSave();
-		if (!_newSong) emit("savedError", _song.youtubeId);
+		if (!_newSong) onSavedError(_song.youtubeId);
 		return new Toast("The video appears to not be working.");
 		return new Toast("The video appears to not be working.");
 	}
 	}
 
 
 	if (!_song.title) {
 	if (!_song.title) {
 		saveButtonRef.handleFailedSave();
 		saveButtonRef.handleFailedSave();
-		if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
+		if (!_newSong || bulk.value) onSavedError(_song.youtubeId);
 		return new Toast("Please fill in all fields");
 		return new Toast("Please fill in all fields");
 	}
 	}
 
 
 	if (!_song.thumbnail) {
 	if (!_song.thumbnail) {
 		saveButtonRef.handleFailedSave();
 		saveButtonRef.handleFailedSave();
-		if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
+		if (!_newSong || bulk.value) onSavedError(_song.youtubeId);
 		return new Toast("Please fill in all fields");
 		return new Toast("Please fill in all fields");
 	}
 	}
 
 
@@ -572,7 +739,7 @@ const save = (songToCopy, closeOrNext, saveButtonRefName, _newSong = false) => {
 		originalSong.value.youtubeId !== _song.youtubeId
 		originalSong.value.youtubeId !== _song.youtubeId
 	) {
 	) {
 		saveButtonRef.handleFailedSave();
 		saveButtonRef.handleFailedSave();
-		if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
+		if (!_newSong || bulk.value) onSavedError(_song.youtubeId);
 		return new Toast(
 		return new Toast(
 			"You're not allowed to change the YouTube id while the player is not working"
 			"You're not allowed to change the YouTube id while the player is not working"
 		);
 		);
@@ -582,11 +749,11 @@ const save = (songToCopy, closeOrNext, saveButtonRefName, _newSong = false) => {
 	if (
 	if (
 		Number(_song.skipDuration) + Number(_song.duration) >
 		Number(_song.skipDuration) + Number(_song.duration) >
 			Number.parseInt(youtubeVideoDuration.value) &&
 			Number.parseInt(youtubeVideoDuration.value) &&
-		(((!_newSong || props.bulk) && !youtubeError.value) ||
+		(((!_newSong || bulk.value) && !youtubeError.value) ||
 			originalSong.value.duration !== _song.duration)
 			originalSong.value.duration !== _song.duration)
 	) {
 	) {
 		saveButtonRef.handleFailedSave();
 		saveButtonRef.handleFailedSave();
-		if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
+		if (!_newSong || bulk.value) onSavedError(_song.youtubeId);
 		return new Toast(
 		return new Toast(
 			"Duration can't be higher than the length of the video"
 			"Duration can't be higher than the length of the video"
 		);
 		);
@@ -595,7 +762,7 @@ const save = (songToCopy, closeOrNext, saveButtonRefName, _newSong = false) => {
 	// Title
 	// Title
 	if (!validation.isLength(_song.title, 1, 100)) {
 	if (!validation.isLength(_song.title, 1, 100)) {
 		saveButtonRef.handleFailedSave();
 		saveButtonRef.handleFailedSave();
-		if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
+		if (!_newSong || bulk.value) onSavedError(_song.youtubeId);
 		return new Toast("Title must have between 1 and 100 characters.");
 		return new Toast("Title must have between 1 and 100 characters.");
 	}
 	}
 
 
@@ -605,7 +772,7 @@ const save = (songToCopy, closeOrNext, saveButtonRefName, _newSong = false) => {
 		_song.artists.length > 10
 		_song.artists.length > 10
 	) {
 	) {
 		saveButtonRef.handleFailedSave();
 		saveButtonRef.handleFailedSave();
-		if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
+		if (!_newSong || bulk.value) onSavedError(_song.youtubeId);
 		return new Toast(
 		return new Toast(
 			"Invalid artists. You must have at least 1 artist and a maximum of 10 artists."
 			"Invalid artists. You must have at least 1 artist and a maximum of 10 artists."
 		);
 		);
@@ -628,7 +795,7 @@ const save = (songToCopy, closeOrNext, saveButtonRefName, _newSong = false) => {
 
 
 	if (error) {
 	if (error) {
 		saveButtonRef.handleFailedSave();
 		saveButtonRef.handleFailedSave();
-		if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
+		if (!_newSong || bulk.value) onSavedError(_song.youtubeId);
 		return new Toast(error);
 		return new Toast(error);
 	}
 	}
 
 
@@ -654,7 +821,7 @@ const save = (songToCopy, closeOrNext, saveButtonRefName, _newSong = false) => {
 
 
 	if (error) {
 	if (error) {
 		saveButtonRef.handleFailedSave();
 		saveButtonRef.handleFailedSave();
-		if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
+		if (!_newSong || bulk.value) onSavedError(_song.youtubeId);
 		return new Toast(error);
 		return new Toast(error);
 	}
 	}
 
 
@@ -674,19 +841,19 @@ const save = (songToCopy, closeOrNext, saveButtonRefName, _newSong = false) => {
 
 
 	if (error) {
 	if (error) {
 		saveButtonRef.handleFailedSave();
 		saveButtonRef.handleFailedSave();
-		if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
+		if (!_newSong || bulk.value) onSavedError(_song.youtubeId);
 		return new Toast(error);
 		return new Toast(error);
 	}
 	}
 
 
 	// Thumbnail
 	// Thumbnail
 	if (!validation.isLength(_song.thumbnail, 1, 256)) {
 	if (!validation.isLength(_song.thumbnail, 1, 256)) {
 		saveButtonRef.handleFailedSave();
 		saveButtonRef.handleFailedSave();
-		if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
+		if (!_newSong || bulk.value) onSavedError(_song.youtubeId);
 		return new Toast("Thumbnail must have between 8 and 256 characters.");
 		return new Toast("Thumbnail must have between 8 and 256 characters.");
 	}
 	}
 	if (useHTTPS.value && _song.thumbnail.indexOf("https://") !== 0) {
 	if (useHTTPS.value && _song.thumbnail.indexOf("https://") !== 0) {
 		saveButtonRef.handleFailedSave();
 		saveButtonRef.handleFailedSave();
-		if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
+		if (!_newSong || bulk.value) onSavedError(_song.youtubeId);
 		return new Toast('Thumbnail must start with "https://".');
 		return new Toast('Thumbnail must start with "https://".');
 	}
 	}
 
 
@@ -696,7 +863,7 @@ const save = (songToCopy, closeOrNext, saveButtonRefName, _newSong = false) => {
 		_song.thumbnail.indexOf("https://") !== 0
 		_song.thumbnail.indexOf("https://") !== 0
 	) {
 	) {
 		saveButtonRef.handleFailedSave();
 		saveButtonRef.handleFailedSave();
-		if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
+		if (!_newSong || bulk.value) onSavedError(_song.youtubeId);
 		return new Toast('Thumbnail must start with "http://".');
 		return new Toast('Thumbnail must start with "http://".');
 	}
 	}
 
 
@@ -708,19 +875,19 @@ const save = (songToCopy, closeOrNext, saveButtonRefName, _newSong = false) => {
 
 
 			if (res.status === "error") {
 			if (res.status === "error") {
 				saveButtonRef.handleFailedSave();
 				saveButtonRef.handleFailedSave();
-				emit("savedError", _song.youtubeId);
+				onSavedError(_song.youtubeId);
 				return;
 				return;
 			}
 			}
 
 
 			saveButtonRef.handleSuccessfulSave();
 			saveButtonRef.handleSuccessfulSave();
-			emit("savedSuccess", _song.youtubeId);
+			onSavedSuccess(_song.youtubeId);
 
 
 			if (!closeOrNext) {
 			if (!closeOrNext) {
 				loadSong(_song.youtubeId);
 				loadSong(_song.youtubeId);
 				return;
 				return;
 			}
 			}
 
 
-			if (props.bulk) emit("nextSong");
+			if (bulk.value) emit("nextSong");
 			else closeCurrentModal();
 			else closeCurrentModal();
 		});
 		});
 	return socket.dispatch(`songs.update`, _song._id, _song, res => {
 	return socket.dispatch(`songs.update`, _song._id, _song, res => {
@@ -728,30 +895,22 @@ const save = (songToCopy, closeOrNext, saveButtonRefName, _newSong = false) => {
 
 
 		if (res.status === "error") {
 		if (res.status === "error") {
 			saveButtonRef.handleFailedSave();
 			saveButtonRef.handleFailedSave();
-			emit("savedError", _song.youtubeId);
+			onSavedError(_song.youtubeId);
 			return;
 			return;
 		}
 		}
 
 
 		updateOriginalSong(_song);
 		updateOriginalSong(_song);
 
 
 		saveButtonRef.handleSuccessfulSave();
 		saveButtonRef.handleSuccessfulSave();
-		emit("savedSuccess", _song.youtubeId);
+		onSavedSuccess(_song.youtubeId);
 
 
 		if (!closeOrNext) return;
 		if (!closeOrNext) return;
 
 
-		if (props.bulk) emit("nextSong");
+		if (bulk.value) emit("nextSong");
 		else closeCurrentModal();
 		else closeCurrentModal();
 	});
 	});
 };
 };
 
 
-const editNextSong = () => {
-	emit("nextSong");
-};
-
-const toggleFlag = () => {
-	emit("toggleFlag");
-};
-
 const getAlbumData = type => {
 const getAlbumData = type => {
 	if (!song.value.discogs) return;
 	if (!song.value.discogs) return;
 	if (type === "title")
 	if (type === "title")
@@ -1068,6 +1227,82 @@ onMounted(async () => {
 		{ modalUuid: props.modalUuid }
 		{ modalUuid: props.modalUuid }
 	);
 	);
 
 
+	if (bulk.value) {
+		socket.dispatch("apis.joinRoom", "edit-songs");
+
+		socket.dispatch(
+			"songs.getSongsFromYoutubeIds",
+			youtubeIds.value,
+			res => {
+				if (res.data.songs.length === 0) {
+					closeCurrentModal();
+					new Toast("You can't edit 0 songs.");
+				} else {
+					items.value = res.data.songs.map(song => ({
+						status: "todo",
+						flagged: false,
+						song
+					}));
+					editNextSong();
+				}
+			}
+		);
+
+		socket.on(
+			`event:admin.song.created`,
+			res => {
+				const index = items.value
+					.map(item => item.song.youtubeId)
+					.indexOf(res.data.song.youtubeId);
+				if (index >= 0)
+					items.value[index].song = {
+						...items.value[index].song,
+						...res.data.song,
+						created: true
+					};
+			},
+			{ modalUuid: props.modalUuid }
+		);
+
+		socket.on(
+			`event:admin.song.updated`,
+			res => {
+				const index = items.value
+					.map(item => item.song.youtubeId)
+					.indexOf(res.data.song.youtubeId);
+				if (index >= 0)
+					items.value[index].song = {
+						...items.value[index].song,
+						...res.data.song,
+						updated: true
+					};
+			},
+			{ modalUuid: props.modalUuid }
+		);
+
+		socket.on(
+			`event:admin.song.removed`,
+			res => {
+				const index = items.value
+					.map(item => item.song._id)
+					.indexOf(res.data.songId);
+				if (index >= 0) items.value[index].song.removed = true;
+			},
+			{ modalUuid: props.modalUuid }
+		);
+
+		socket.on(
+			`event:admin.youtubeVideo.removed`,
+			res => {
+				const index = items.value
+					.map(item => item.song.youtubeVideoId)
+					.indexOf(res.videoId);
+				if (index >= 0) items.value[index].song.removed = true;
+			},
+			{ modalUuid: props.modalUuid }
+		);
+	}
+
 	keyboardShortcuts.registerShortcut("editSong.pauseResumeVideo", {
 	keyboardShortcuts.registerShortcut("editSong.pauseResumeVideo", {
 		keyCode: 101,
 		keyCode: 101,
 		preventDefault: true,
 		preventDefault: true,
@@ -1194,10 +1429,7 @@ onMounted(async () => {
 			if (
 			if (
 				modals.value[
 				modals.value[
 					activeModals.value[activeModals.value.length - 1]
 					activeModals.value[activeModals.value.length - 1]
-				] === "editSong" ||
-				modals.value[
-					activeModals.value[activeModals.value.length - 1]
-				] === "editSongs"
+				] === "editSong"
 			) {
 			) {
 				onCloseModal();
 				onCloseModal();
 			}
 			}
@@ -1232,6 +1464,10 @@ onMounted(async () => {
 });
 });
 
 
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {
+	if (bulk.value) {
+		socket.dispatch("apis.leaveRoom", "edit-songs");
+	}
+
 	unloadSong(youtubeId.value, song.value._id);
 	unloadSong(youtubeId.value, song.value._id);
 
 
 	playerReady.value = false;
 	playerReady.value = false;
@@ -1274,11 +1510,175 @@ onBeforeUnmount(() => {
 			:intercept-close="true"
 			:intercept-close="true"
 			@close="onCloseModal"
 			@close="onCloseModal"
 		>
 		>
-			<template #toggleMobileSidebar>
-				<slot name="toggleMobileSidebar" />
+			<template #toggleMobileSidebar v-if="bulk">
+				<i
+					class="material-icons toggle-sidebar-icon"
+					:content="`${
+						sidebarMobileActive ? 'Close' : 'Open'
+					} Edit Queue`"
+					v-tippy
+					@click="toggleMobileSidebar()"
+					>expand_circle_down</i
+				>
 			</template>
 			</template>
-			<template #sidebar>
-				<slot name="sidebar" />
+			<template #sidebar v-if="bulk">
+				<div class="sidebar" :class="{ active: sidebarMobileActive }">
+					<header class="sidebar-head">
+						<h2 class="sidebar-title is-marginless">Edit Queue</h2>
+						<i
+							class="material-icons toggle-sidebar-icon"
+							:content="`${
+								sidebarMobileActive ? 'Close' : 'Open'
+							} Edit Queue`"
+							v-tippy
+							@click="toggleMobileSidebar()"
+							>expand_circle_down</i
+						>
+					</header>
+					<section class="sidebar-body">
+						<div
+							v-show="filteredItems.length > 0"
+							class="edit-songs-items"
+						>
+							<div
+								class="item"
+								v-for="(
+									{ status, flagged, song }, index
+								) in filteredItems"
+								:key="`edit-songs-item-${index}`"
+								:ref="
+									el =>
+										(songItems[
+											`edit-songs-item-${song.youtubeId}`
+										] = el)
+								"
+							>
+								<song-item
+									:song="song"
+									:thumbnail="false"
+									:duration="false"
+									:disabled-actions="
+										song.removed
+											? ['all']
+											: ['report', 'edit']
+									"
+									:class="{
+										updated: song.updated,
+										removed: song.removed
+									}"
+								>
+									<template #leftIcon>
+										<i
+											v-if="
+												currentSong.youtubeId ===
+													song.youtubeId &&
+												!song.removed
+											"
+											class="material-icons item-icon editing-icon"
+											content="Currently editing song"
+											v-tippy="{ theme: 'info' }"
+											@click="toggleDone(index)"
+											>edit</i
+										>
+										<i
+											v-else-if="song.removed"
+											class="material-icons item-icon removed-icon"
+											content="Song removed"
+											v-tippy="{ theme: 'info' }"
+											>delete_forever</i
+										>
+										<i
+											v-else-if="status === 'error'"
+											class="material-icons item-icon error-icon"
+											content="Error saving song"
+											v-tippy="{ theme: 'info' }"
+											@click="toggleDone(index)"
+											>error</i
+										>
+										<i
+											v-else-if="status === 'saving'"
+											class="material-icons item-icon saving-icon"
+											content="Currently saving song"
+											v-tippy="{ theme: 'info' }"
+											>pending</i
+										>
+										<i
+											v-else-if="flagged"
+											class="material-icons item-icon flag-icon"
+											content="Song flagged"
+											v-tippy="{ theme: 'info' }"
+											@click="toggleDone(index)"
+											>flag_circle</i
+										>
+										<i
+											v-else-if="status === 'done'"
+											class="material-icons item-icon done-icon"
+											content="Song marked complete"
+											v-tippy="{ theme: 'info' }"
+											@click="toggleDone(index)"
+											>check_circle</i
+										>
+										<i
+											v-else-if="status === 'todo'"
+											class="material-icons item-icon todo-icon"
+											content="Song marked todo"
+											v-tippy="{ theme: 'info' }"
+											@click="toggleDone(index)"
+											>cancel</i
+										>
+									</template>
+									<template v-if="!song.removed" #actions>
+										<i
+											class="material-icons edit-icon"
+											content="Edit Song"
+											v-tippy
+											@click="pickSong(song)"
+										>
+											edit
+										</i>
+									</template>
+									<template #tippyActions>
+										<i
+											class="material-icons flag-icon"
+											:class="{
+												flagged
+											}"
+											content="Toggle Flag"
+											v-tippy
+											@click="toggleFlag(index)"
+										>
+											flag_circle
+										</i>
+									</template>
+								</song-item>
+							</div>
+						</div>
+						<p v-if="filteredItems.length === 0" class="no-items">
+							{{
+								flagFilter
+									? "No flagged songs queued"
+									: "No songs queued"
+							}}
+						</p>
+					</section>
+					<footer class="sidebar-foot">
+						<button
+							@click="toggleFlagFilter()"
+							class="button is-primary"
+						>
+							{{
+								flagFilter
+									? "Show All Songs"
+									: "Show Only Flagged Songs"
+							}}
+						</button>
+					</footer>
+				</div>
+				<div
+					v-if="sidebarMobileActive"
+					class="sidebar-overlay"
+					@click="toggleMobileSidebar()"
+				></div>
 			</template>
 			</template>
 			<template #body>
 			<template #body>
 				<div v-if="!youtubeId && !newSong" class="notice-container">
 				<div v-if="!youtubeId && !newSong" class="notice-container">
@@ -1911,7 +2311,7 @@ onBeforeUnmount(() => {
 						@click="toggleFlag()"
 						@click="toggleFlag()"
 						v-if="youtubeId && !songDeleted"
 						v-if="youtubeId && !songDeleted"
 					>
 					>
-						{{ flagged ? "Unflag" : "Flag" }}
+						{{ currentSongFlagged ? "Unflag" : "Flag" }}
 					</button>
 					</button>
 				</div>
 				</div>
 				<div v-if="!newSong && !songDeleted">
 				<div v-if="!newSong && !songDeleted">
@@ -2017,6 +2417,39 @@ onBeforeUnmount(() => {
 	.duration-canvas {
 	.duration-canvas {
 		background-color: var(--dark-grey-2) !important;
 		background-color: var(--dark-grey-2) !important;
 	}
 	}
+
+	.sidebar {
+		.sidebar-head,
+		.sidebar-foot {
+			background-color: var(--dark-grey-3);
+			border: none;
+		}
+
+		.sidebar-body {
+			background-color: var(--dark-grey-4) !important;
+		}
+
+		.sidebar-head .toggle-sidebar-icon.material-icons,
+		.sidebar-title {
+			color: var(--white);
+		}
+
+		p,
+		label,
+		td,
+		th {
+			color: var(--light-grey-2) !important;
+		}
+
+		h1,
+		h2,
+		h3,
+		h4,
+		h5,
+		h6 {
+			color: var(--white) !important;
+		}
+	}
 }
 }
 
 
 .modal-card-body {
 .modal-card-body {
@@ -2506,4 +2939,161 @@ onBeforeUnmount(() => {
 :deep(.autosuggest-container) {
 :deep(.autosuggest-container) {
 	top: unset;
 	top: unset;
 }
 }
+
+.toggle-sidebar-icon {
+	display: none;
+}
+
+.sidebar {
+	width: 100%;
+	max-width: 350px;
+	z-index: 2000;
+	display: flex;
+	flex-direction: column;
+	position: relative;
+	height: 100%;
+	max-height: calc(100vh - 40px);
+	overflow: auto;
+	margin-right: 8px;
+	border-radius: @border-radius;
+
+	.sidebar-head,
+	.sidebar-foot {
+		display: flex;
+		flex-shrink: 0;
+		position: relative;
+		justify-content: flex-start;
+		align-items: center;
+		padding: 20px;
+		background-color: var(--light-grey);
+	}
+
+	.sidebar-head {
+		border-bottom: 1px solid var(--light-grey-2);
+		border-radius: @border-radius @border-radius 0 0;
+
+		.sidebar-title {
+			display: flex;
+			flex: 1;
+			margin: 0;
+			font-size: 26px;
+			font-weight: 600;
+		}
+	}
+
+	.sidebar-body {
+		background-color: var(--white);
+		display: flex;
+		flex-direction: column;
+		row-gap: 8px;
+		flex: 1;
+		overflow: auto;
+		padding: 10px;
+
+		.edit-songs-items {
+			display: flex;
+			flex-direction: column;
+			row-gap: 8px;
+
+			.item {
+				display: flex;
+				flex-direction: row;
+				align-items: center;
+				column-gap: 8px;
+
+				:deep(.song-item) {
+					.item-icon {
+						margin-right: 10px;
+						cursor: pointer;
+					}
+
+					.removed-icon,
+					.error-icon {
+						color: var(--red);
+					}
+
+					.saving-icon,
+					.todo-icon,
+					.editing-icon {
+						color: var(--primary-color);
+					}
+
+					.done-icon {
+						color: var(--green);
+					}
+
+					.flag-icon {
+						color: var(--orange);
+
+						&.flagged {
+							color: var(--grey);
+						}
+					}
+
+					&.removed {
+						filter: grayscale(100%);
+						cursor: not-allowed;
+						user-select: none;
+					}
+				}
+			}
+		}
+
+		.no-items {
+			text-align: center;
+			font-size: 18px;
+		}
+	}
+
+	.sidebar-foot {
+		border-top: 1px solid var(--light-grey-2);
+		border-radius: 0 0 @border-radius @border-radius;
+
+		.button {
+			flex: 1;
+		}
+	}
+
+	.sidebar-overlay {
+		display: none;
+	}
+}
+
+@media only screen and (max-width: 1580px) {
+	.toggle-sidebar-icon {
+		display: flex;
+		margin-right: 5px;
+		transform: rotate(90deg);
+		cursor: pointer;
+	}
+
+	.sidebar {
+		display: none;
+
+		&.active {
+			display: flex;
+			position: absolute;
+			z-index: 2010;
+			top: 20px;
+			left: 20px;
+
+			.sidebar-head .toggle-sidebar-icon {
+				display: flex;
+				margin-left: 5px;
+				transform: rotate(-90deg);
+			}
+		}
+	}
+
+	.sidebar-overlay {
+		display: flex;
+		position: absolute;
+		z-index: 2009;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background-color: rgba(10, 10, 10, 0.85);
+	}
+}
 </style>
 </style>

+ 0 - 671
frontend/src/components/modals/EditSongs.vue

@@ -1,671 +0,0 @@
-<script setup lang="ts">
-import { storeToRefs } from "pinia";
-import {
-	defineAsyncComponent,
-	ref,
-	computed,
-	onMounted,
-	onBeforeUnmount,
-	onUnmounted
-} from "vue";
-import Toast from "toasters";
-import { useModalsStore } from "@/stores/modals";
-import { useEditSongStore } from "@/stores/editSong";
-import { useEditSongsStore } from "@/stores/editSongs";
-import { useWebsocketsStore } from "@/stores/websockets";
-import { Song } from "@/types/song.js";
-
-const EditSongModal = defineAsyncComponent(
-	() => import("@/components/modals/EditSong/index.vue")
-);
-const SongItem = defineAsyncComponent(
-	() => import("@/components/SongItem.vue")
-);
-
-const props = defineProps({
-	modalUuid: { type: String, default: "" }
-});
-
-const editSongStore = useEditSongStore(props);
-const editSongsStore = useEditSongsStore(props);
-
-const { socket } = useWebsocketsStore();
-
-const { youtubeIds, songPrefillData } = storeToRefs(editSongsStore);
-
-const { editSong } = editSongStore;
-
-const { openModal, closeCurrentModal } = useModalsStore();
-
-const items = ref([]);
-const currentSong = ref(<Song>{});
-const flagFilter = ref(false);
-const sidebarMobileActive = ref(false);
-const songItems = ref([]);
-
-const editingItemIndex = computed(() =>
-	items.value.findIndex(
-		item => item.song.youtubeId === currentSong.value.youtubeId
-	)
-);
-const filteredItems = computed({
-	get: () =>
-		items.value.filter(item => (flagFilter.value ? item.flagged : true)),
-	set: (newItem: any) => {
-		const index = items.value.findIndex(
-			item => item.song.youtubeId === newItem.youtubeId
-		);
-		items.value[index] = newItem;
-	}
-});
-const filteredEditingItemIndex = computed(() =>
-	filteredItems.value.findIndex(
-		item => item.song.youtubeId === currentSong.value.youtubeId
-	)
-);
-const currentSongFlagged = computed(
-	() =>
-		items.value.find(
-			item => item.song.youtubeId === currentSong.value.youtubeId
-		)?.flagged
-);
-
-const pickSong = song => {
-	editSong({
-		youtubeId: song.youtubeId,
-		prefill: songPrefillData.value[song.youtubeId]
-	});
-	currentSong.value = song;
-	if (
-		songItems.value[`edit-songs-item-${song.youtubeId}`] &&
-		songItems.value[`edit-songs-item-${song.youtubeId}`][0]
-	)
-		songItems.value[
-			`edit-songs-item-${song.youtubeId}`
-		][0].scrollIntoView();
-};
-
-const editNextSong = () => {
-	const currentlyEditingSongIndex = filteredEditingItemIndex.value;
-	let newEditingSongIndex = -1;
-	const index =
-		currentlyEditingSongIndex + 1 === filteredItems.value.length
-			? 0
-			: currentlyEditingSongIndex + 1;
-	for (let i = index; i < filteredItems.value.length; i += 1) {
-		if (!flagFilter.value || filteredItems.value[i].flagged) {
-			newEditingSongIndex = i;
-			break;
-		}
-	}
-
-	if (newEditingSongIndex > -1) {
-		const nextSong = filteredItems.value[newEditingSongIndex].song;
-		if (nextSong.removed) editNextSong();
-		else pickSong(nextSong);
-	}
-};
-
-const toggleFlag = (songIndex = null) => {
-	if (songIndex && songIndex > -1) {
-		filteredItems.value[songIndex].flagged =
-			!filteredItems.value[songIndex].flagged;
-		new Toast(
-			`Successfully ${
-				filteredItems.value[songIndex].flagged ? "flagged" : "unflagged"
-			} song.`
-		);
-	} else if (!songIndex && editingItemIndex.value > -1) {
-		items.value[editingItemIndex.value].flagged =
-			!items.value[editingItemIndex.value].flagged;
-		new Toast(
-			`Successfully ${
-				items.value[editingItemIndex.value].flagged
-					? "flagged"
-					: "unflagged"
-			} song.`
-		);
-	}
-};
-
-const onSavedSuccess = youtubeId => {
-	const itemIndex = items.value.findIndex(
-		item => item.song.youtubeId === youtubeId
-	);
-	if (itemIndex > -1) {
-		items.value[itemIndex].status = "done";
-		items.value[itemIndex].flagged = false;
-	}
-};
-
-const onSavedError = youtubeId => {
-	const itemIndex = items.value.findIndex(
-		item => item.song.youtubeId === youtubeId
-	);
-	if (itemIndex > -1) items.value[itemIndex].status = "error";
-};
-
-const onSaving = youtubeId => {
-	const itemIndex = items.value.findIndex(
-		item => item.song.youtubeId === youtubeId
-	);
-	if (itemIndex > -1) items.value[itemIndex].status = "saving";
-};
-
-const toggleDone = (index, overwrite = null) => {
-	const { status } = filteredItems.value[index];
-
-	if (status === "done" && overwrite !== "done")
-		filteredItems.value[index].status = "todo";
-	else {
-		filteredItems.value[index].status = "done";
-		filteredItems.value[index].flagged = false;
-	}
-};
-
-const toggleFlagFilter = () => {
-	flagFilter.value = !flagFilter.value;
-};
-
-const toggleMobileSidebar = () => {
-	sidebarMobileActive.value = !sidebarMobileActive.value;
-};
-
-const onClose = () => {
-	const doneItems = items.value.filter(item => item.status === "done").length;
-	const flaggedItems = items.value.filter(item => item.flagged).length;
-	const notDoneItems = items.value.length - doneItems;
-
-	if (doneItems > 0 && notDoneItems > 0)
-		openModal({
-			modal: "confirm",
-			data: {
-				message:
-					"You have songs which are not done yet. Are you sure you want to stop editing songs?",
-				onCompleted: closeCurrentModal
-			}
-		});
-	else if (flaggedItems > 0)
-		openModal({
-			modal: "confirm",
-			data: {
-				message:
-					"You have songs which are flagged. Are you sure you want to stop editing songs?",
-				onCompleted: closeCurrentModal
-			}
-		});
-	else closeCurrentModal();
-};
-
-// onBeforeMount(() => {
-// 	console.log("EDITSONGS BEFOREMOUNT");
-//  store.registerModule(
-//  	["modals", "editSongs", props.modalUuid, "editSong"],
-//  	editSongStore
-//  );
-// });
-
-onMounted(async () => {
-	console.log("EDITSONGS MOUNTED");
-
-	socket.dispatch("apis.joinRoom", "edit-songs");
-
-	socket.dispatch("songs.getSongsFromYoutubeIds", youtubeIds.value, res => {
-		if (res.data.songs.length === 0) {
-			closeCurrentModal();
-			new Toast("You can't edit 0 songs.");
-		} else {
-			items.value = res.data.songs.map(song => ({
-				status: "todo",
-				flagged: false,
-				song
-			}));
-			editNextSong();
-		}
-	});
-
-	socket.on(
-		`event:admin.song.created`,
-		res => {
-			const index = items.value
-				.map(item => item.song.youtubeId)
-				.indexOf(res.data.song.youtubeId);
-			if (index >= 0)
-				items.value[index].song = {
-					...items.value[index].song,
-					...res.data.song,
-					created: true
-				};
-		},
-		{ modalUuid: props.modalUuid }
-	);
-
-	socket.on(
-		`event:admin.song.updated`,
-		res => {
-			const index = items.value
-				.map(item => item.song.youtubeId)
-				.indexOf(res.data.song.youtubeId);
-			if (index >= 0)
-				items.value[index].song = {
-					...items.value[index].song,
-					...res.data.song,
-					updated: true
-				};
-		},
-		{ modalUuid: props.modalUuid }
-	);
-
-	socket.on(
-		`event:admin.song.removed`,
-		res => {
-			const index = items.value
-				.map(item => item.song._id)
-				.indexOf(res.data.songId);
-			if (index >= 0) items.value[index].song.removed = true;
-		},
-		{ modalUuid: props.modalUuid }
-	);
-
-	socket.on(
-		`event:admin.youtubeVideo.removed`,
-		res => {
-			const index = items.value
-				.map(item => item.song.youtubeVideoId)
-				.indexOf(res.videoId);
-			if (index >= 0) items.value[index].song.removed = true;
-		},
-		{ modalUuid: props.modalUuid }
-	);
-});
-
-onBeforeUnmount(() => {
-	console.log("EDITSONGS BEFORE UNMOUNT");
-	socket.dispatch("apis.leaveRoom", "edit-songs");
-});
-
-onUnmounted(() => {
-	console.log("EDITSONGS UNMOUNTED");
-	// Delete the Pinia store that was created for this modal, after all other cleanup tasks are performed
-	editSongsStore.$dispose();
-});
-</script>
-
-<template>
-	<div>
-		<edit-song-modal
-			:modal-module-path="`modals/editSongs/${modalUuid}/editSong`"
-			:modal-uuid="modalUuid"
-			:bulk="true"
-			:flagged="currentSongFlagged"
-			v-if="currentSong"
-			@saved-success="onSavedSuccess"
-			@saved-error="onSavedError"
-			@saving="onSaving"
-			@toggle-flag="toggleFlag"
-			@next-song="editNextSong"
-			@close="onClose"
-		>
-			<template #toggleMobileSidebar>
-				<i
-					class="material-icons toggle-sidebar-icon"
-					:content="`${
-						sidebarMobileActive ? 'Close' : 'Open'
-					} Edit Queue`"
-					v-tippy
-					@click="toggleMobileSidebar()"
-					>expand_circle_down</i
-				>
-			</template>
-			<template #sidebar>
-				<div class="sidebar" :class="{ active: sidebarMobileActive }">
-					<header class="sidebar-head">
-						<h2 class="sidebar-title is-marginless">Edit Queue</h2>
-						<i
-							class="material-icons toggle-sidebar-icon"
-							:content="`${
-								sidebarMobileActive ? 'Close' : 'Open'
-							} Edit Queue`"
-							v-tippy
-							@click="toggleMobileSidebar()"
-							>expand_circle_down</i
-						>
-					</header>
-					<section class="sidebar-body">
-						<div
-							v-show="filteredItems.length > 0"
-							class="edit-songs-items"
-						>
-							<div
-								class="item"
-								v-for="(
-									{ status, flagged, song }, index
-								) in filteredItems"
-								:key="`edit-songs-item-${index}`"
-								:ref="
-									el =>
-										(songItems[
-											`edit-songs-item-${song.youtubeId}`
-										] = el)
-								"
-							>
-								<song-item
-									:song="song"
-									:thumbnail="false"
-									:duration="false"
-									:disabled-actions="
-										song.removed
-											? ['all']
-											: ['report', 'edit']
-									"
-									:class="{
-										updated: song.updated,
-										removed: song.removed
-									}"
-								>
-									<template #leftIcon>
-										<i
-											v-if="
-												currentSong.youtubeId ===
-													song.youtubeId &&
-												!song.removed
-											"
-											class="material-icons item-icon editing-icon"
-											content="Currently editing song"
-											v-tippy="{ theme: 'info' }"
-											@click="toggleDone(index)"
-											>edit</i
-										>
-										<i
-											v-else-if="song.removed"
-											class="material-icons item-icon removed-icon"
-											content="Song removed"
-											v-tippy="{ theme: 'info' }"
-											>delete_forever</i
-										>
-										<i
-											v-else-if="status === 'error'"
-											class="material-icons item-icon error-icon"
-											content="Error saving song"
-											v-tippy="{ theme: 'info' }"
-											@click="toggleDone(index)"
-											>error</i
-										>
-										<i
-											v-else-if="status === 'saving'"
-											class="material-icons item-icon saving-icon"
-											content="Currently saving song"
-											v-tippy="{ theme: 'info' }"
-											>pending</i
-										>
-										<i
-											v-else-if="flagged"
-											class="material-icons item-icon flag-icon"
-											content="Song flagged"
-											v-tippy="{ theme: 'info' }"
-											@click="toggleDone(index)"
-											>flag_circle</i
-										>
-										<i
-											v-else-if="status === 'done'"
-											class="material-icons item-icon done-icon"
-											content="Song marked complete"
-											v-tippy="{ theme: 'info' }"
-											@click="toggleDone(index)"
-											>check_circle</i
-										>
-										<i
-											v-else-if="status === 'todo'"
-											class="material-icons item-icon todo-icon"
-											content="Song marked todo"
-											v-tippy="{ theme: 'info' }"
-											@click="toggleDone(index)"
-											>cancel</i
-										>
-									</template>
-									<template v-if="!song.removed" #actions>
-										<i
-											class="material-icons edit-icon"
-											content="Edit Song"
-											v-tippy
-											@click="pickSong(song)"
-										>
-											edit
-										</i>
-									</template>
-									<template #tippyActions>
-										<i
-											class="material-icons flag-icon"
-											:class="{ flagged }"
-											content="Toggle Flag"
-											v-tippy
-											@click="toggleFlag(index)"
-										>
-											flag_circle
-										</i>
-									</template>
-								</song-item>
-							</div>
-						</div>
-						<p v-if="filteredItems.length === 0" class="no-items">
-							{{
-								flagFilter
-									? "No flagged songs queued"
-									: "No songs queued"
-							}}
-						</p>
-					</section>
-					<footer class="sidebar-foot">
-						<button
-							@click="toggleFlagFilter()"
-							class="button is-primary"
-						>
-							{{
-								flagFilter
-									? "Show All Songs"
-									: "Show Only Flagged Songs"
-							}}
-						</button>
-					</footer>
-				</div>
-				<div
-					v-if="sidebarMobileActive"
-					class="sidebar-overlay"
-					@click="toggleMobileSidebar()"
-				></div>
-			</template>
-		</edit-song-modal>
-	</div>
-</template>
-
-<style lang="less" scoped>
-.night-mode .sidebar {
-	.sidebar-head,
-	.sidebar-foot {
-		background-color: var(--dark-grey-3);
-		border: none;
-	}
-
-	.sidebar-body {
-		background-color: var(--dark-grey-4) !important;
-	}
-
-	.sidebar-head .toggle-sidebar-icon.material-icons,
-	.sidebar-title {
-		color: var(--white);
-	}
-
-	p,
-	label,
-	td,
-	th {
-		color: var(--light-grey-2) !important;
-	}
-
-	h1,
-	h2,
-	h3,
-	h4,
-	h5,
-	h6 {
-		color: var(--white) !important;
-	}
-}
-
-.toggle-sidebar-icon {
-	display: none;
-}
-
-.sidebar {
-	width: 100%;
-	max-width: 350px;
-	z-index: 2000;
-	display: flex;
-	flex-direction: column;
-	position: relative;
-	height: 100%;
-	max-height: calc(100vh - 40px);
-	overflow: auto;
-	margin-right: 8px;
-	border-radius: @border-radius;
-
-	.sidebar-head,
-	.sidebar-foot {
-		display: flex;
-		flex-shrink: 0;
-		position: relative;
-		justify-content: flex-start;
-		align-items: center;
-		padding: 20px;
-		background-color: var(--light-grey);
-	}
-
-	.sidebar-head {
-		border-bottom: 1px solid var(--light-grey-2);
-		border-radius: @border-radius @border-radius 0 0;
-
-		.sidebar-title {
-			display: flex;
-			flex: 1;
-			margin: 0;
-			font-size: 26px;
-			font-weight: 600;
-		}
-	}
-
-	.sidebar-body {
-		background-color: var(--white);
-		display: flex;
-		flex-direction: column;
-		row-gap: 8px;
-		flex: 1;
-		overflow: auto;
-		padding: 10px;
-
-		.edit-songs-items {
-			display: flex;
-			flex-direction: column;
-			row-gap: 8px;
-
-			.item {
-				display: flex;
-				flex-direction: row;
-				align-items: center;
-				column-gap: 8px;
-
-				:deep(.song-item) {
-					.item-icon {
-						margin-right: 10px;
-						cursor: pointer;
-					}
-
-					.removed-icon,
-					.error-icon {
-						color: var(--red);
-					}
-
-					.saving-icon,
-					.todo-icon,
-					.editing-icon {
-						color: var(--primary-color);
-					}
-
-					.done-icon {
-						color: var(--green);
-					}
-
-					.flag-icon {
-						color: var(--orange);
-
-						&.flagged {
-							color: var(--grey);
-						}
-					}
-
-					&.removed {
-						filter: grayscale(100%);
-						cursor: not-allowed;
-						user-select: none;
-					}
-				}
-			}
-		}
-
-		.no-items {
-			text-align: center;
-			font-size: 18px;
-		}
-	}
-
-	.sidebar-foot {
-		border-top: 1px solid var(--light-grey-2);
-		border-radius: 0 0 @border-radius @border-radius;
-
-		.button {
-			flex: 1;
-		}
-	}
-
-	.sidebar-overlay {
-		display: none;
-	}
-}
-
-@media only screen and (max-width: 1580px) {
-	.toggle-sidebar-icon {
-		display: flex;
-		margin-right: 5px;
-		transform: rotate(90deg);
-		cursor: pointer;
-	}
-
-	.sidebar {
-		display: none;
-
-		&.active {
-			display: flex;
-			position: absolute;
-			z-index: 2010;
-			top: 20px;
-			left: 20px;
-
-			.sidebar-head .toggle-sidebar-icon {
-				display: flex;
-				margin-left: 5px;
-				transform: rotate(-90deg);
-			}
-		}
-	}
-
-	.sidebar-overlay {
-		display: flex;
-		position: absolute;
-		z-index: 2009;
-		top: 0;
-		left: 0;
-		right: 0;
-		bottom: 0;
-		background-color: rgba(10, 10, 10, 0.85);
-	}
-}
-</style>

+ 1 - 1
frontend/src/components/modals/ImportAlbum.vue

@@ -124,7 +124,7 @@ const startEditingSongs = () => {
 	if (songsToEdit.value.length === 0) new Toast("You can't edit 0 songs.");
 	if (songsToEdit.value.length === 0) new Toast("You can't edit 0 songs.");
 	else {
 	else {
 		openModal({
 		openModal({
-			modal: "editSongs",
+			modal: "editSong",
 			data: { songs: songsToEdit.value }
 			data: { songs: songsToEdit.value }
 		});
 		});
 	}
 	}

+ 1 - 1
frontend/src/pages/Admin/Songs/Import.vue

@@ -344,7 +344,7 @@ const editSongs = videos => {
 	const songs = videos.map(youtubeId => ({ youtubeId }));
 	const songs = videos.map(youtubeId => ({ youtubeId }));
 	if (songs.length === 1)
 	if (songs.length === 1)
 		openModal({ modal: "editSong", data: { song: songs[0] } });
 		openModal({ modal: "editSong", data: { song: songs[0] } });
-	else openModal({ modal: "editSongs", data: { songs } });
+	else openModal({ modal: "editSong", data: { songs } });
 };
 };
 
 
 const importAlbum = youtubeIds => {
 const importAlbum = youtubeIds => {

+ 1 - 1
frontend/src/pages/Admin/Songs/index.vue

@@ -317,7 +317,7 @@ const editMany = selectedRows => {
 		const songs = selectedRows.map(row => ({
 		const songs = selectedRows.map(row => ({
 			youtubeId: row.youtubeId
 			youtubeId: row.youtubeId
 		}));
 		}));
-		openModal({ modal: "editSongs", data: { songs } });
+		openModal({ modal: "editSong", data: { songs } });
 	}
 	}
 };
 };
 
 

+ 1 - 1
frontend/src/pages/Admin/YouTube/Videos.vue

@@ -203,7 +203,7 @@ const editMany = selectedRows => {
 		const songs = selectedRows.map(row => ({
 		const songs = selectedRows.map(row => ({
 			youtubeId: row.youtubeId
 			youtubeId: row.youtubeId
 		}));
 		}));
-		openModal({ modal: "editSongs", data: { songs } });
+		openModal({ modal: "editSong", data: { songs } });
 	}
 	}
 };
 };
 
 

+ 15 - 3
frontend/src/stores/editSong.ts

@@ -20,11 +20,23 @@ export const useEditSongStore = props => {
 			reports: <Report[]>[],
 			reports: <Report[]>[],
 			tab: "discogs",
 			tab: "discogs",
 			newSong: false,
 			newSong: false,
-			prefillData: {}
+			prefillData: {},
+			bulk: false,
+			youtubeIds: [],
+			songPrefillData: {}
 		}),
 		}),
 		actions: {
 		actions: {
-			init({ song }) {
-				this.editSong(song);
+			init({ song, songs }) {
+				if (songs) {
+					this.bulk = true;
+					this.youtubeIds = songs.map(song => song.youtubeId);
+					this.songPrefillData = Object.fromEntries(
+						songs.map(song => [
+							song.youtubeId,
+							song.prefill ? song.prefill : {}
+						])
+					);
+				} else this.editSong(song);
 			},
 			},
 			showTab(tab) {
 			showTab(tab) {
 				this.tab = tab;
 				this.tab = tab;

+ 0 - 26
frontend/src/stores/editSongs.ts

@@ -1,26 +0,0 @@
-import { defineStore } from "pinia";
-
-export const useEditSongsStore = props => {
-	const { modalUuid } = props;
-	return defineStore(`editSongs-${modalUuid}`, {
-		state: () => ({
-			youtubeIds: [],
-			songPrefillData: {}
-		}),
-		actions: {
-			init({ songs }) {
-				this.youtubeIds = songs.map(song => song.youtubeId);
-				this.songPrefillData = Object.fromEntries(
-					songs.map(song => [
-						song.youtubeId,
-						song.prefill ? song.prefill : {}
-					])
-				);
-			}
-			// 	resetSongs(state) {
-			// 	this.youtubeIds = [];
-			// 	this.songPrefillData = {};
-			// }
-		}
-	})();
-};

+ 0 - 4
frontend/src/stores/modals.ts

@@ -4,7 +4,6 @@ import ws from "@/ws";
 
 
 import { useEditUserStore } from "@/stores/editUser";
 import { useEditUserStore } from "@/stores/editUser";
 import { useEditSongStore } from "@/stores/editSong";
 import { useEditSongStore } from "@/stores/editSong";
-import { useEditSongsStore } from "@/stores/editSongs";
 import { useBulkActionsStore } from "@/stores/bulkActions";
 import { useBulkActionsStore } from "@/stores/bulkActions";
 import { useConfirmStore } from "@/stores/confirm";
 import { useConfirmStore } from "@/stores/confirm";
 import { useCreateStationStore } from "@/stores/createStation";
 import { useCreateStationStore } from "@/stores/createStation";
@@ -77,9 +76,6 @@ export const useModalsStore = defineStore("modals", {
 				case "editSong":
 				case "editSong":
 					store = useEditSongStore({ modalUuid: uuid });
 					store = useEditSongStore({ modalUuid: uuid });
 					break;
 					break;
-				case "editSongs":
-					store = useEditSongsStore({ modalUuid: uuid });
-					break;
 				case "bulkActions":
 				case "bulkActions":
 					store = useBulkActionsStore({ modalUuid: uuid });
 					store = useBulkActionsStore({ modalUuid: uuid });
 					break;
 					break;