Browse Source

feat: Started adding Import Songs admin page

Owen Diffey 2 years ago
parent
commit
1835e69f52

+ 17 - 12
backend/logic/actions/songs.js

@@ -1152,18 +1152,23 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					YouTubeModule.runJob(
-						"GET_PLAYLIST",
-						{
-							url,
-							musicOnly
-						},
-						this
-					)
-						.then(res => {
-							next(null, res.songs);
-						})
-						.catch(next);
+					const playlistRegex = /[\\?&]list=([^&#]*)/;
+					const channelRegex =
+						/\.[\w]+\/(?:(?:channel\/(UC[0-9A-Za-z_-]{21}[AQgw]))|(?:user\/?([\w-]+))|(?:c\/?([\w-]+))|(?:\/?([\w-]+)))/;
+					if (playlistRegex.exec(url) || channelRegex.exec(url))
+						YouTubeModule.runJob(
+							playlistRegex.exec(url) ? "GET_PLAYLIST" : "GET_CHANNEL",
+							{
+								url,
+								musicOnly
+							},
+							this
+						)
+							.then(res => {
+								next(null, res.songs);
+							})
+							.catch(next);
+					else next("Invalid YouTube URL.");
 				},
 				(youtubeIds, next) => {
 					let successful = 0;

+ 2 - 2
backend/logic/youtube.js

@@ -519,7 +519,7 @@ class _YouTubeModule extends CoreClass {
 			if (payload.nextPageToken) params.pageToken = payload.nextPageToken;
 
 			YouTubeModule.runJob(
-				"GET_PLAYLIST_ITEMS",
+				"API_GET_PLAYLIST_ITEMS",
 				{
 					params
 				},
@@ -644,7 +644,7 @@ class _YouTubeModule extends CoreClass {
 							.catch(err => next(err));
 					},
 
-					next => {
+					(playlistId, next) => {
 						let songs = [];
 						let nextPageToken = "";
 

+ 5 - 1
frontend/src/main.js

@@ -167,7 +167,11 @@ const router = createRouter({
 			children: [
 				{
 					path: "songs",
-					component: () => import("@/pages/Admin/Songs.vue")
+					component: () => import("@/pages/Admin/Songs/index.vue")
+				},
+				{
+					path: "songs/import",
+					component: () => import("@/pages/Admin/Songs/Import.vue")
 				},
 				{
 					path: "reports",

+ 236 - 0
frontend/src/pages/Admin/Songs/Import.vue

@@ -0,0 +1,236 @@
+<template>
+	<div>
+		<divage-metadata title="Admin | Songs | Import" />
+		<div class="admin-tab import-tab">
+			<h1>Import Songs</h1>
+			<p>Import songs from YouTube playlists or channels</p>
+			<hr class="section-horizontal-rule" />
+
+			<div class="start-new-import">
+				<h4>Start New Import</h4>
+				<hr class="section-horizontal-rule" />
+
+				<div v-if="createImport.stage === 1" class="stage">
+					<label class="label">Import Method</label>
+					<div class="control is-expanded select">
+						<select v-model="createImport.importMethod">
+							<option value="youtube">YouTube</option>
+						</select>
+					</div>
+
+					<div class="control is-expanded">
+						<button
+							class="button is-info next-form"
+							@click.prevent="submitCreateImport(1)"
+						>
+							<i class="material-icons icon-with-button"
+								>navigate_next</i
+							>
+							Next
+						</button>
+					</div>
+				</div>
+
+				<div
+					v-else-if="
+						createImport.stage === 2 &&
+						createImport.importMethod === 'youtube'
+					"
+					class="stage"
+				>
+					<label class="label">YouTube URL</label>
+					<div class="control is-expanded">
+						<input
+							class="input"
+							type="text"
+							placeholder="YouTube Playlist or Channel URL"
+							v-model="createImport.youtubeUrl"
+						/>
+					</div>
+
+					<label class="label">Video Type</label>
+					<div class="control is-expanded select">
+						<select v-model="createImport.isImportingOnlyMusic">
+							<option :value="false">Import all</option>
+							<option :value="true">Import only music</option>
+						</select>
+					</div>
+
+					<div class="control is-expanded">
+						<button
+							class="button is-info"
+							@click.prevent="submitCreateImport(2)"
+						>
+							<i class="material-icons icon-with-button"
+								>publish</i
+							>
+							Import
+						</button>
+					</div>
+				</div>
+
+				<div v-if="createImport.stage === 3" class="stage">
+					<p>Import Started</p>
+
+					<div class="control is-expanded">
+						<button
+							class="button is-info"
+							@click.prevent="submitCreateImport(3)"
+						>
+							<i class="material-icons icon-with-button"
+								>restart_alt</i
+							>
+							Start Again
+						</button>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import { mapGetters } from "vuex";
+
+import Toast from "toasters";
+
+export default {
+	data() {
+		return {
+			createImport: {
+				stage: 1,
+				importMethod: "youtube",
+				youtubeUrl:
+					"https://www.youtube.com/playlist?list=PL3-sRm8xAzY9gpXTMGVHJWy_FMD67NBed",
+				isImportingOnlyMusic: false
+			}
+		};
+	},
+	computed: {
+		...mapGetters({
+			socket: "websockets/getSocket"
+		})
+	},
+	methods: {
+		submitCreateImport(stage) {
+			console.log(111, `Submitted stage ${stage}`, this.createImport);
+
+			if (stage === 2) {
+				const playlistRegex = /[\\?&]list=([^&#]*)/;
+				const channelRegex =
+					/\.[\w]+\/(?:(?:channel\/(UC[0-9A-Za-z_-]{21}[AQgw]))|(?:user\/?([\w-]+))|(?:c\/?([\w-]+))|(?:\/?([\w-]+)))/;
+				if (
+					playlistRegex.exec(this.createImport.youtubeUrl) ||
+					channelRegex.exec(this.createImport.youtubeUrl)
+				)
+					this.importFromYoutube();
+				else
+					return new Toast({
+						content: "Please enter a valid YouTube URL.",
+						timeout: 4000
+					});
+			}
+
+			if (stage === 3) this.resetCreateImport();
+			else this.createImport.stage += 1;
+
+			return this.createImport.stage;
+		},
+		resetCreateImport() {
+			this.createImport = {
+				stage: 1,
+				importMethod: "youtube",
+				youtubeUrl:
+					"https://www.youtube.com/channel/UCio_FVgKVgqcHrRiXDpnqbw",
+				isImportingOnlyMusic: false
+			};
+		},
+		importFromYoutube() {
+			let isImportingPlaylist = true;
+
+			if (!this.createImport.youtubeUrl)
+				return new Toast("Please enter a YouTube URL.");
+
+			// don't give starting import message instantly in case of instant error
+			setTimeout(() => {
+				if (isImportingPlaylist) {
+					new Toast(
+						"Starting to import your playlist. This can take some time to do."
+					);
+				}
+			}, 750);
+
+			return this.socket.dispatch(
+				"songs.requestSet",
+				this.createImport.youtubeUrl,
+				this.createImport.isImportingOnlyMusic,
+				true,
+				res => {
+					isImportingPlaylist = false;
+
+					return new Toast({
+						content: res.message,
+						timeout: 20000
+					});
+				}
+			);
+		}
+	}
+};
+</script>
+
+<style lang="less" scoped>
+.night-mode .admin-tab.import-tab {
+	background-color: var(--dark-grey-3);
+
+	p {
+		color: var(--light-grey-2);
+	}
+
+	.start-new-import {
+		background-color: var(--dark-grey-2) !important;
+	}
+}
+
+.admin-tab.import-tab {
+	display: flex;
+	flex-grow: 1;
+	flex-direction: column;
+	padding: 20px !important;
+	border-radius: @border-radius;
+	background-color: var(--white);
+	color: var(--dark-grey);
+	box-shadow: @box-shadow;
+
+	h1 {
+		font-size: 36px;
+		margin: 0 0 5px 0;
+	}
+
+	hr {
+		margin: 10px 0;
+	}
+
+	.start-new-import {
+		max-width: 600px;
+		margin: 10px 0;
+		padding: 10px;
+		border-radius: 5px;
+		box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1),
+			0 0 0 1px rgba(10, 10, 10, 0.1);
+
+		h4 {
+			font-size: 22px;
+			margin: 5px 0;
+		}
+
+		.control.is-expanded .button {
+			width: 100%;
+
+			&.next-form .material-icons {
+				font-size: 24px;
+			}
+		}
+	}
+}
+</style>

+ 0 - 0
frontend/src/pages/Admin/Songs.vue → frontend/src/pages/Admin/Songs/index.vue


+ 39 - 0
frontend/src/pages/Admin/index.vue

@@ -21,7 +21,46 @@
 								<i class="material-icons">menu_open</i>
 								<span>Minimise</span>
 							</div>
+							<div
+								v-if="sidebarActive"
+								class="sidebar-item with-children"
+								:class="{ 'is-active': childrenActive.songs }"
+							>
+								<span>
+									<router-link to="/admin/songs">
+										<i class="material-icons">music_note</i>
+										<span>Songs</span>
+									</router-link>
+									<i
+										class="material-icons toggle-sidebar-children"
+										@click="
+											toggleChildren({ child: 'songs' })
+										"
+									>
+										{{
+											childrenActive.songs
+												? "expand_less"
+												: "expand_more"
+										}}
+									</i>
+								</span>
+								<div class="sidebar-item-children">
+									<router-link
+										class="sidebar-item-child"
+										to="/admin/songs"
+									>
+										Songs
+									</router-link>
+									<router-link
+										class="sidebar-item-child"
+										to="/admin/songs/import"
+									>
+										Import
+									</router-link>
+								</div>
+							</div>
 							<router-link
+								v-else
 								class="sidebar-item songs"
 								to="/admin/songs"
 								content="Songs"