| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 | <template>	<modal title="Edit Playlist">		<div slot="body">			<nav class="level">				<div class="level-item has-text-centered">					<div>						<p class="heading">							Total Length						</p>						<p class="title">							{{ totalLength() }}						</p>					</div>				</div>			</nav>			<hr />			<aside class="menu">				<ul class="menu-list">					<li v-for="(song, index) in playlist.songs" :key="index">						<a href="#" target="_blank">{{ song.title }}</a>						<div class="controls">							<a href="#" v-on:click="promoteSong(song.songId)">								<i class="material-icons" v-if="index > 0"									>keyboard_arrow_up</i								>								<i									v-else									class="material-icons"									style="opacity: 0"									>error</i								>							</a>							<a href="#" v-on:click="demoteSong(song.songId)">								<i									v-if="playlist.songs.length - 1 !== index"									class="material-icons"									>keyboard_arrow_down</i								>								<i									v-else									class="material-icons"									style="opacity: 0"									>error</i								>							</a>							<a								href="#"								v-on:click="removeSongFromPlaylist(song.songId)"							>								<i class="material-icons">delete</i>							</a>						</div>					</li>				</ul>				<br />			</aside>			<div class="control is-grouped">				<p class="control is-expanded">					<input						v-model="songQuery"						class="input"						type="text"						placeholder="Search for Song to add"						autofocus						@keyup.enter="searchForSongs()"					/>				</p>				<p class="control">					<a class="button is-info" @click="searchForSongs()" href="#"						>Search</a					>				</p>			</div>			<table v-if="songQueryResults.length > 0" class="table">				<tbody>					<tr						v-for="(result, index) in songQueryResults"						:key="index"					>						<td>							<img :src="result.thumbnail" />						</td>						<td>{{ result.title }}</td>						<td>							<a								class="button is-success"								href="#"								@click="addSongToPlaylist(result.id)"								>Add</a							>						</td>					</tr>				</tbody>			</table>			<div class="control is-grouped">				<p class="control is-expanded">					<input						v-model="importQuery"						class="input"						type="text"						placeholder="YouTube Playlist URL"						@keyup.enter="importPlaylist()"					/>				</p>				<p class="control">					<a class="button is-info" @click="importPlaylist()" href="#"						>Import</a					>				</p>			</div>			<h5>Edit playlist details:</h5>			<div class="control is-grouped">				<p class="control is-expanded">					<input						v-model="playlist.displayName"						class="input"						type="text"						placeholder="Playlist Display Name"						@keyup.enter="renamePlaylist()"					/>				</p>				<p class="control">					<a class="button is-info" @click="renamePlaylist()" href="#"						>Rename</a					>				</p>			</div>		</div>		<div slot="footer">			<a class="button is-danger" v-on:click="removePlaylist()" href="#"				>Remove Playlist</a			>		</div>	</modal></template><script>import { mapState } from "vuex";import { Toast } from "vue-roaster";import Modal from "../Modal.vue";import io from "../../../io";import validation from "../../../validation";export default {	components: { Modal },	data() {		return {			playlist: { songs: [] },			songQueryResults: [],			songQuery: "",			importQuery: ""		};	},	computed: mapState("user/playlists", {		editing: state => state.editing	}),	mounted() {		io.getSocket(socket => {			this.socket = socket;			this.socket.emit("playlists.getPlaylist", this.editing, res => {				if (res.status === "success") this.playlist = res.data;				this.playlist.oldId = res.data._id;			});			this.socket.on("event:playlist.addSong", data => {				if (this.playlist._id === data.playlistId)					this.playlist.songs.push(data.song);			});			this.socket.on("event:playlist.removeSong", data => {				if (this.playlist._id === data.playlistId) {					this.playlist.songs.forEach((song, index) => {						if (song.songId === data.songId)							this.playlist.songs.splice(index, 1);					});				}			});			this.socket.on("event:playlist.updateDisplayName", data => {				if (this.playlist._id === data.playlistId)					this.playlist.displayName = data.displayName;			});			this.socket.on("event:playlist.moveSongToBottom", data => {				if (this.playlist._id === data.playlistId) {					let songIndex;					this.playlist.songs.forEach((song, index) => {						if (song.songId === data.songId) songIndex = index;					});					const song = this.playlist.songs.splice(songIndex, 1)[0];					this.playlist.songs.push(song);				}			});			this.socket.on("event:playlist.moveSongToTop", data => {				if (this.playlist._id === data.playlistId) {					let songIndex;					this.playlist.songs.forEach((song, index) => {						if (song.songId === data.songId) songIndex = index;					});					const song = this.playlist.songs.splice(songIndex, 1)[0];					this.playlist.songs.unshift(song);				}			});		});	},	methods: {		formatTime(length) {			const duration = moment.duration(length, "seconds");			const getHours = () => {				return Math.floor(duration.asHours());			};			if (length <= 0) return "0 seconds";			const formatHours = () => {				if (getHours() > 0) {					if (getHours() > 1) {						if (getHours() < 10) return `0${getHours()} hours `;						return `${getHours()} hours `;					}					return `0${getHours()} hour `;				}				return "";			};			const formatMinutes = () => {				if (duration.minutes() > 0) {					if (duration.minutes() > 1) {						if (duration.minutes() < 10)							return `0${duration.minutes()} minutes `;						return `${duration.minutes()} minutes `;					}					return `0${duration.minutes()} minute `;				}				return "";			};			const formatSeconds = () => {				if (duration.seconds() > 0) {					if (duration.seconds() > 1) {						if (duration.seconds() < 10)							return `0${duration.seconds()} seconds `;						return `${duration.seconds()} seconds `;					}					return `0${duration.seconds()} second `;				}				return "";			};			return formatHours() + formatMinutes() + formatSeconds();		},		totalLength() {			let length = 0;			this.playlist.songs.forEach(song => {				length += song.duration;			});			return this.formatTime(length);		},		searchForSongs() {			let query = this.songQuery;			if (query.indexOf("&index=") !== -1) {				query = query.split("&index=");				query.pop();				query = query.join("");			}			if (query.indexOf("&list=") !== -1) {				query = query.split("&list=");				query.pop();				query = query.join("");			}			this.socket.emit("apis.searchYoutube", query, res => {				if (res.status === "success") {					this.songQueryResults = [];					for (let i = 0; i < res.data.items.length; i += 1) {						this.songQueryResults.push({							id: res.data.items[i].id.videoId,							url: `https://www.youtube.com/watch?v=${this.id}`,							title: res.data.items[i].snippet.title,							thumbnail:								res.data.items[i].snippet.thumbnails.default.url						});					}				} else if (res.status === "error")					Toast.methods.addToast(res.message, 3000);			});		},		addSongToPlaylist(id) {			this.socket.emit(				"playlists.addSongToPlaylist",				id,				this.playlist._id,				res => {					Toast.methods.addToast(res.message, 4000);				}			);		},		importPlaylist() {			Toast.methods.addToast(				"Starting to import your playlist. This can take some time to do.",				4000			);			this.socket.emit(				"playlists.addSetToPlaylist",				this.importQuery,				this.playlist._id,				res => {					if (res.status === "success")						this.playlist.songs = res.data;					Toast.methods.addToast(res.message, 4000);				}			);		},		removeSongFromPlaylist(id) {			this.socket.emit(				"playlists.removeSongFromPlaylist",				id,				this.playlist._id,				res => {					Toast.methods.addToast(res.message, 4000);				}			);		},		renamePlaylist() {			const { displayName } = this.playlist;			if (!validation.isLength(displayName, 2, 32))				return Toast.methods.addToast(					"Display name must have between 2 and 32 characters.",					8000				);			if (!validation.regex.azAZ09_.test(displayName))				return Toast.methods.addToast(					"Invalid display name format. Allowed characters: a-z, A-Z, 0-9 and _.",					8000				);			return this.socket.emit(				"playlists.updateDisplayName",				this.playlist._id,				this.playlist.displayName,				res => {					Toast.methods.addToast(res.message, 4000);				}			);		},		removePlaylist() {			this.socket.emit("playlists.remove", this.playlist._id, res => {				Toast.methods.addToast(res.message, 3000);				if (res.status === "success") {					this.$parent.modals.editPlaylist = !this.$parent.modals						.editPlaylist;				}			});		},		promoteSong(songId) {			this.socket.emit(				"playlists.moveSongToTop",				this.playlist._id,				songId,				res => {					Toast.methods.addToast(res.message, 4000);				}			);		},		demoteSong(songId) {			this.socket.emit(				"playlists.moveSongToBottom",				this.playlist._id,				songId,				res => {					Toast.methods.addToast(res.message, 4000);				}			);		}	}};</script><style lang="scss" scoped>@import "styles/global.scss";.menu {	padding: 0 20px;}.menu-list li {	display: flex;	justify-content: space-between;}.menu-list a:hover {	color: #000 !important;}li a {	display: flex;	align-items: center;}.controls {	display: flex;	a {		display: flex;		align-items: center;	}}.table {	margin-bottom: 0;}h5 {	padding: 20px 0;}</style>
 |