Browse Source

refactor: vue-roaster updated

Signed-off-by: Jonathan <theflametrooper@gmail.com>
Jonathan 5 years ago
parent
commit
376a83d5db
33 changed files with 562 additions and 577 deletions
  1. 2 2
      README.md
  2. 2 1
      frontend/.eslintrc
  3. 0 16
      frontend/.snyk
  4. 8 6
      frontend/App.vue
  5. 2 2
      frontend/api/auth.js
  6. 26 18
      frontend/components/Admin/News.vue
  7. 2 2
      frontend/components/Admin/Punishments.vue
  8. 5 5
      frontend/components/Admin/QueueSongs.vue
  9. 6 6
      frontend/components/Admin/Reports.vue
  10. 7 4
      frontend/components/Admin/Songs.vue
  11. 31 19
      frontend/components/Admin/Stations.vue
  12. 3 3
      frontend/components/Modals/AddSongToPlaylist.vue
  13. 25 13
      frontend/components/Modals/AddSongToQueue.vue
  14. 38 34
      frontend/components/Modals/CreateCommunityStation.vue
  15. 11 4
      frontend/components/Modals/EditNews.vue
  16. 74 60
      frontend/components/Modals/EditSong.vue
  17. 51 34
      frontend/components/Modals/EditStation.vue
  18. 32 27
      frontend/components/Modals/EditUser.vue
  19. 4 2
      frontend/components/Modals/Login.vue
  20. 12 10
      frontend/components/Modals/Playlists/Create.vue
  21. 24 21
      frontend/components/Modals/Playlists/Edit.vue
  22. 4 2
      frontend/components/Modals/Register.vue
  23. 2 2
      frontend/components/Modals/Report.vue
  24. 6 3
      frontend/components/Sidebars/Playlist.vue
  25. 7 6
      frontend/components/Sidebars/SongsList.vue
  26. 65 48
      frontend/components/Station/Station.vue
  27. 16 7
      frontend/components/User/ResetPassword.vue
  28. 65 56
      frontend/components/User/Settings.vue
  29. 6 6
      frontend/components/User/Show.vue
  30. 11 11
      frontend/components/pages/Home.vue
  31. 1 0
      frontend/dist/index.tpl.html
  32. 1 1
      frontend/package.json
  33. 13 146
      yarn.lock

+ 2 - 2
README.md

@@ -289,8 +289,8 @@ Run this command in your shell. You will have to do this command for every shell
 You can call Toasts using our custom package, [`vue-roaster`](https://github.com/atjonathan/vue-roaster), using the following code:
 
 ```js
-import { Toast } from "vue-roaster";
-Toast.methods.addToast("", 0);
+import Toast from "vue-roaster";
+new Toast({ content: "", persistant: true });
 ```
 
 ### Set user role

+ 2 - 1
frontend/.eslintrc

@@ -29,6 +29,7 @@
 		"no-underscore-dangle": 0,
 		"radix": 0,
 		"no-multi-assign": 0,
-		"no-shadow": 0
+		"no-shadow": 0,
+		"no-new": 0
 	}
 }

+ 0 - 16
frontend/.snyk

@@ -2,20 +2,4 @@
 version: v1.13.5
 # ignores vulnerabilities until expiry date; change duration by modifying expiry date
 ignore:
-  'npm:vue:20170401':
-    - vue-roaster > vue:
-        reason: temp
-        expires: '2019-09-04T02:07:16.079Z'
-  'npm:vue:20170829':
-    - vue-roaster > vue:
-        reason: temp
-        expires: '2019-09-04T02:07:16.079Z'
-  'npm:vue:20180222':
-    - vue-roaster > vue:
-        reason: temp
-        expires: '2019-09-04T02:07:16.079Z'
-  'npm:vue:20180802':
-    - vue-roaster > vue:
-        reason: temp
-        expires: '2019-09-04T02:07:16.079Z'
 patch: {}

+ 8 - 6
frontend/App.vue

@@ -7,7 +7,6 @@
 			</h1>
 			<!-- should be a persistant toast -->
 			<router-view />
-			<toast />
 			<what-is-new />
 			<mobile-alert />
 			<login-modal v-if="modals.header.login" />
@@ -19,7 +18,7 @@
 <script>
 import { mapState, mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 
 import Banned from "./components/pages/Banned.vue";
 import WhatIsNew from "./components/Modals/WhatIsNew.vue";
@@ -87,7 +86,7 @@ export default {
 					.replace(new RegExp("<", "g"), "&lt;")
 					.replace(new RegExp(">", "g"), "&gt;");
 				this.$router.push({ query: {} });
-				Toast.methods.addToast(err, 20000);
+				new Toast({ content: err, timeout: 20000 });
 			}
 			if (this.$route.query.msg) {
 				let { msg } = this.$route.query;
@@ -95,7 +94,7 @@ export default {
 					.replace(new RegExp("<", "g"), "&lt;")
 					.replace(new RegExp(">", "g"), "&gt;");
 				this.$router.push({ query: {} });
-				Toast.methods.addToast(msg, 20000);
+				new Toast({ content: msg, timeout: 20000 });
 			}
 		});
 		io.getSocket(true, socket => {
@@ -105,7 +104,6 @@ export default {
 		});
 	},
 	components: {
-		Toast,
 		WhatIsNew,
 		MobileAlert,
 		LoginModal,
@@ -118,10 +116,14 @@ export default {
 <style lang="scss">
 @import "styles/global.scss";
 
-#toast-container {
+#toasts-container {
 	z-index: 10000 !important;
 }
 
+.toast:not(:first-of-type) {
+	margin-top: 5px;
+}
+
 html {
 	overflow: auto !important;
 }

+ 2 - 2
frontend/api/auth.js

@@ -1,4 +1,4 @@
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 import io from "../io";
 
 // when Vuex needs to interact with socket.io
@@ -84,7 +84,7 @@ export default {
 							return window.location.reload();
 						});
 					}
-					Toast.methods.addToast(result.message, 4000);
+					new Toast({ content: result.message, timeout: 4000 });
 					return reject(new Error(result.message));
 				});
 			});

+ 26 - 18
frontend/components/Admin/News.vue

@@ -215,7 +215,7 @@
 <script>
 import { mapActions, mapState } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 import io from "../../io";
 
 import EditNews from "../Modals/EditNews.vue";
@@ -267,28 +267,28 @@ export default {
 			} = this;
 
 			if (this.creating.title === "")
-				return Toast.methods.addToast(
-					"Field (Title) cannot be empty",
-					3000
-				);
+				return new Toast({
+					content: "Field (Title) cannot be empty",
+					timeout: 3000
+				});
 			if (this.creating.description === "")
-				return Toast.methods.addToast(
-					"Field (Description) cannot be empty",
-					3000
-				);
+				return new Toast({
+					content: "Field (Description) cannot be empty",
+					timeout: 3000
+				});
 			if (
 				bugs.length <= 0 &&
 				features.length <= 0 &&
 				improvements.length <= 0 &&
 				upcoming.length <= 0
 			)
-				return Toast.methods.addToast(
-					"You must have at least one News Item",
-					3000
-				);
+				return new Toast({
+					content: "You must have at least one News Item",
+					timeout: 3000
+				});
 
 			return this.socket.emit("news.create", this.creating, result => {
-				Toast.methods.addToast(result.message, 4000);
+				new Toast(result.message, 4000);
 				if (result.status === "success")
 					this.creating = {
 						title: "",
@@ -301,8 +301,10 @@ export default {
 			});
 		},
 		removeNews(news) {
-			this.socket.emit("news.remove", news, res =>
-				Toast.methods.addToast(res.message, 8000)
+			this.socket.emit(
+				"news.remove",
+				news,
+				res => new Toast({ content: res.message, timeout: 8000 })
 			);
 		},
 		editNewsClick(news) {
@@ -313,14 +315,20 @@ export default {
 			const change = document.getElementById(`new-${type}`).value.trim();
 
 			if (this.creating[type].indexOf(change) !== -1)
-				return Toast.methods.addToast(`Tag already exists`, 3000);
+				return new Toast({
+					content: `Tag already exists`,
+					timeout: 3000
+				});
 
 			if (change) {
 				document.getElementById(`new-${type}`).value = "";
 				this.creating[type].push(change);
 				return true;
 			}
-			return Toast.methods.addToast(`${type} cannot be empty`, 3000);
+			return new Toast({
+				content: `${type} cannot be empty`,
+				timeout: 3000
+			});
 		},
 		removeChange(type, index) {
 			this.creating[type].splice(index, 1);

+ 2 - 2
frontend/components/Admin/Punishments.vue

@@ -113,7 +113,7 @@
 
 <script>
 import { mapState, mapActions } from "vuex";
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 
 import ViewPunishment from "../Modals/ViewPunishment.vue";
 import io from "../../io";
@@ -149,7 +149,7 @@ export default {
 				this.ipBan.reason,
 				this.ipBan.expiresAt,
 				res => {
-					Toast.methods.addToast(res.message, 6000);
+					new Toast({ content: res.message, timeout: 6000 });
 				}
 			);
 		},

+ 5 - 5
frontend/components/Admin/QueueSongs.vue

@@ -98,7 +98,7 @@
 <script>
 import { mapState, mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 
 import EditSong from "../Modals/EditSong.vue";
 import UserIdToUsername from "../UserIdToUsername.vue";
@@ -149,15 +149,15 @@ export default {
 		add(song) {
 			this.socket.emit("songs.add", song, res => {
 				if (res.status === "success")
-					Toast.methods.addToast(res.message, 2000);
-				else Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 2000 });
+				else new Toast({ content: res.message, timeout: 4000 });
 			});
 		},
 		remove(id) {
 			this.socket.emit("queueSongs.remove", id, res => {
 				if (res.status === "success")
-					Toast.methods.addToast(res.message, 2000);
-				else Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 2000 });
+				else new Toast({ content: res.message, timeout: 4000 });
 			});
 		},
 		getSet() {

+ 6 - 6
frontend/components/Admin/Reports.vue

@@ -59,7 +59,7 @@
 <script>
 import { mapState, mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 import io from "../../io";
 
 import IssuesModal from "../Modals/IssuesModal.vue";
@@ -96,10 +96,10 @@ export default {
 			this.socket.emit("reports.findOne", this.$route.query.id, res => {
 				if (res.status === "success") this.view(res.data);
 				else
-					Toast.methods.addToast(
-						"Report with that ID not found",
-						3000
-					);
+					new Toast({
+						content: "Report with that ID not found",
+						timeout: 3000
+					});
 			});
 		}
 	},
@@ -118,7 +118,7 @@ export default {
 		},
 		resolve(reportId) {
 			this.socket.emit("reports.resolve", reportId, res => {
-				Toast.methods.addToast(res.message, 3000);
+				new Toast({ content: res.message, timeout: 3000 });
 				if (res.status === "success" && this.modals.viewReport)
 					this.closeModal({
 						sector: "admin",

+ 7 - 4
frontend/components/Admin/Songs.vue

@@ -102,7 +102,7 @@
 <script>
 import { mapState, mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 
 import EditSong from "../Modals/EditSong.vue";
 import UserIdToUsername from "../UserIdToUsername.vue";
@@ -153,8 +153,8 @@ export default {
 		remove(id) {
 			this.socket.emit("songs.remove", id, res => {
 				if (res.status === "success")
-					Toast.methods.addToast(res.message, 4000);
-				else Toast.methods.addToast(res.message, 8000);
+					new Toast({ content: res.message, timeout: 4000 });
+				else new Toast({ content: res.message, timeout: 8000 });
 			});
 		},
 		getSet() {
@@ -232,7 +232,10 @@ export default {
 					this.edit(res.data);
 					this.closeModal({ sector: "admin", modal: "viewReport" });
 				} else
-					Toast.methods.addToast("Song with that ID not found", 3000);
+					new Toast({
+						content: "Song with that ID not found",
+						timeout: 3000
+					});
 			});
 		}
 	}

+ 31 - 19
frontend/components/Admin/Stations.vue

@@ -184,7 +184,7 @@
 <script>
 import { mapState, mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 import io from "../../io";
 
 import EditStation from "../Modals/EditStation.vue";
@@ -219,20 +219,20 @@ export default {
 			} = this;
 
 			if (name === undefined)
-				return Toast.methods.addToast(
-					"Field (Name) cannot be empty",
-					3000
-				);
+				return new Toast({
+					content: "Field (Name) cannot be empty",
+					timeout: 3000
+				});
 			if (displayName === undefined)
-				return Toast.methods.addToast(
-					"Field (Display Name) cannot be empty",
-					3000
-				);
+				return new Toast({
+					content: "Field (Display Name) cannot be empty",
+					timeout: 3000
+				});
 			if (description === undefined)
-				return Toast.methods.addToast(
-					"Field (Description) cannot be empty",
-					3000
-				);
+				return new Toast({
+					content: "Field (Description) cannot be empty",
+					timeout: 3000
+				});
 
 			return this.socket.emit(
 				"stations.create",
@@ -245,7 +245,7 @@ export default {
 					blacklistedGenres
 				},
 				result => {
-					Toast.methods.addToast(result.message, 3000);
+					new Toast({ content: result.message, timeout: 3000 });
 					if (result.status === "success")
 						this.newStation = {
 							genres: [],
@@ -259,7 +259,7 @@ export default {
 				"stations.remove",
 				this.stations[index]._id,
 				res => {
-					Toast.methods.addToast(res.message, 3000);
+					new Toast({ content: res.message, timeout: 3000 });
 				}
 			);
 		},
@@ -286,13 +286,19 @@ export default {
 				.value.toLowerCase()
 				.trim();
 			if (this.newStation.genres.indexOf(genre) !== -1)
-				return Toast.methods.addToast("Genre already exists", 3000);
+				return new Toast({
+					content: "Genre already exists",
+					timeout: 3000
+				});
 			if (genre) {
 				this.newStation.genres.push(genre);
 				document.getElementById(`new-genre`).value = "";
 				return true;
 			}
-			return Toast.methods.addToast("Genre cannot be empty", 3000);
+			return new Toast({
+				content: "Genre cannot be empty",
+				timeout: 3000
+			});
 		},
 		removeGenre(index) {
 			this.newStation.genres.splice(index, 1);
@@ -303,14 +309,20 @@ export default {
 				.value.toLowerCase()
 				.trim();
 			if (this.newStation.blacklistedGenres.indexOf(genre) !== -1)
-				return Toast.methods.addToast("Genre already exists", 3000);
+				return new Toast({
+					content: "Genre already exists",
+					timeout: 3000
+				});
 
 			if (genre) {
 				this.newStation.blacklistedGenres.push(genre);
 				document.getElementById(`new-blacklisted-genre`).value = "";
 				return true;
 			}
-			return Toast.methods.addToast("Genre cannot be empty", 3000);
+			return new Toast({
+				content: "Genre cannot be empty",
+				timeout: 3000
+			});
 		},
 		removeBlacklistedGenre(index) {
 			this.newStation.blacklistedGenres.splice(index, 1);

+ 3 - 3
frontend/components/Modals/AddSongToPlaylist.vue

@@ -40,7 +40,7 @@
 <script>
 import { mapState } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 import Modal from "./Modal.vue";
 import io from "../../io";
 
@@ -81,7 +81,7 @@ export default {
 				this.currentSong.songId,
 				playlistId,
 				res => {
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 4000 });
 					if (res.status === "success") {
 						this.playlists[playlistId].songs.push(this.song);
 					}
@@ -95,7 +95,7 @@ export default {
 				this.songId,
 				playlistId,
 				res => {
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 4000 });
 					if (res.status === "success") {
 						this.playlists[playlistId].songs.forEach(
 							(song, index) => {

+ 25 - 13
frontend/components/Modals/AddSongToQueue.vue

@@ -94,7 +94,7 @@
 <script>
 import { mapState, mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 import Modal from "./Modal.vue";
 import io from "../../io";
 
@@ -138,31 +138,43 @@ export default {
 					songId,
 					data => {
 						if (data.status !== "success")
-							Toast.methods.addToast(
-								`Error: ${data.message}`,
-								8000
-							);
-						else Toast.methods.addToast(`${data.message}`, 4000);
+							new Toast({
+								content: `Error: ${data.message}`,
+								timeout: 8000
+							});
+						else
+							new Toast({
+								content: `${data.message}`,
+								timeout: 4000
+							});
 					}
 				);
 			} else {
 				this.socket.emit("queueSongs.add", songId, data => {
 					if (data.status !== "success")
-						Toast.methods.addToast(`Error: ${data.message}`, 8000);
-					else Toast.methods.addToast(`${data.message}`, 4000);
+						new Toast({
+							content: `Error: ${data.message}`,
+							timeout: 8000
+						});
+					else
+						new Toast({
+							content: `${data.message}`,
+							timeout: 4000
+						});
 				});
 			}
 		},
 		importPlaylist() {
-			Toast.methods.addToast(
-				"Starting to import your playlist. This can take some time to do.",
-				4000
-			);
+			new Toast({
+				content:
+					"Starting to import your playlist. This can take some time to do.",
+				timeout: 4000
+			});
 			this.socket.emit(
 				"queueSongs.addSetToQueue",
 				this.importQuery,
 				res => {
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 4000 });
 				}
 			);
 		},

+ 38 - 34
frontend/components/Modals/CreateCommunityStation.vue

@@ -41,7 +41,7 @@
 <script>
 import { mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 import Modal from "./Modal.vue";
 import io from "../../io";
 import validation from "../../validation";
@@ -67,39 +67,43 @@ export default {
 			const { name, displayName, description } = this.newCommunity;
 
 			if (!name || !displayName || !description)
-				return Toast.methods.addToast(
-					"Please fill in all fields",
-					8000
-				);
+				return new Toast({
+					content: "Please fill in all fields",
+					timeout: 8000
+				});
 
 			if (!validation.isLength(name, 2, 16))
-				return Toast.methods.addToast(
-					"Name must have between 2 and 16 characters.",
-					8000
-				);
+				return new Toast({
+					content: "Name must have between 2 and 16 characters.",
+					timeout: 8000
+				});
 
 			if (!validation.regex.az09_.test(name))
-				return Toast.methods.addToast(
-					"Invalid name format. Allowed characters: a-z, 0-9 and _.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Invalid name format. Allowed characters: a-z, 0-9 and _.",
+					timeout: 8000
+				});
 
 			if (!validation.isLength(displayName, 2, 32))
-				return Toast.methods.addToast(
-					"Display name must have between 2 and 32 characters.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Display name must have between 2 and 32 characters.",
+					timeout: 8000
+				});
 			if (!validation.regex.ascii.test(displayName))
-				return Toast.methods.addToast(
-					"Invalid display name format. Only ASCII characters are allowed.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Invalid display name format. Only ASCII characters are allowed.",
+					timeout: 8000
+				});
 
 			if (!validation.isLength(description, 2, 200))
-				return Toast.methods.addToast(
-					"Description must have between 2 and 200 characters.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Description must have between 2 and 200 characters.",
+					timeout: 8000
+				});
 
 			let characters = description.split("");
 
@@ -108,10 +112,10 @@ export default {
 			});
 
 			if (characters.length !== 0)
-				return Toast.methods.addToast(
-					"Invalid description format.",
-					8000
-				);
+				return new Toast({
+					content: "Invalid description format.",
+					timeout: 8000
+				});
 
 			return this.socket.emit(
 				"stations.create",
@@ -123,15 +127,15 @@ export default {
 				},
 				res => {
 					if (res.status === "success") {
-						Toast.methods.addToast(
-							`You have added the station successfully`,
-							4000
-						);
+						new Toast({
+							content: `You have added the station successfully`,
+							timeout: 4000
+						});
 						this.closeModal({
 							sector: "home",
 							modal: "createCommunityStation"
 						});
-					} else Toast.methods.addToast(res.message, 4000);
+					} else new Toast({ content: res.message, timeout: 4000 });
 				}
 			);
 		},

+ 11 - 4
frontend/components/Modals/EditNews.vue

@@ -169,7 +169,7 @@
 <script>
 import { mapActions, mapState } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 import io from "../../io";
 
 import Modal from "./Modal.vue";
@@ -186,10 +186,17 @@ export default {
 			const change = document.getElementById(`edit-${type}`).value.trim();
 
 			if (this.editing[type].indexOf(change) !== -1)
-				return Toast.methods.addToast(`Tag already exists`, 3000);
+				return new Toast({
+					content: `Tag already exists`,
+					timeout: 3000
+				});
 
 			if (change) this.addChange({ type, change });
-			else Toast.methods.addToast(`${type} cannot be empty`, 3000);
+			else
+				new Toast({
+					content: `${type} cannot be empty`,
+					timeout: 3000
+				});
 
 			document.getElementById(`edit-${type}`).value = "";
 			return true;
@@ -203,7 +210,7 @@ export default {
 				this.editing._id,
 				this.editing,
 				res => {
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 4000 });
 					if (res.status === "success") {
 						if (close)
 							this.closeModal({

+ 74 - 60
frontend/components/Modals/EditSong.vue

@@ -532,7 +532,7 @@
 
 <script>
 import { mapState, mapActions } from "vuex";
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 
 import io from "../../io";
 import validation from "../../validation";
@@ -633,40 +633,42 @@ export default {
 			const song = JSON.parse(JSON.stringify(songToCopy));
 
 			if (!song.title)
-				return Toast.methods.addToast(
-					"Please fill in all fields",
-					8000
-				);
+				return new Toast({
+					content: "Please fill in all fields",
+					timeout: 8000
+				});
 			if (!song.thumbnail)
-				return Toast.methods.addToast(
-					"Please fill in all fields",
-					8000
-				);
+				return new Toast({
+					content: "Please fill in all fields",
+					timeout: 8000
+				});
 
 			// Duration
 			if (
 				Number(song.skipDuration) + Number(song.duration) >
 				this.youtubeVideoDuration
 			) {
-				return Toast.methods.addToast(
-					"Duration can't be higher than the length of the video",
-					8000
-				);
+				return new Toast({
+					content:
+						"Duration can't be higher than the length of the video",
+					timeout: 8000
+				});
 			}
 
 			// Title
 			if (!validation.isLength(song.title, 1, 100))
-				return Toast.methods.addToast(
-					"Title must have between 1 and 100 characters.",
-					8000
-				);
+				return new Toast({
+					content: "Title must have between 1 and 100 characters.",
+					timeout: 8000
+				});
 
 			// Artists
 			if (song.artists.length < 1 || song.artists.length > 10)
-				return Toast.methods.addToast(
-					"Invalid artists. You must have at least 1 artist and a maximum of 10 artists.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Invalid artists. You must have at least 1 artist and a maximum of 10 artists.",
+					timeout: 8000
+				});
 			let error;
 			song.artists.forEach(artist => {
 				if (!validation.isLength(artist, 1, 64)) {
@@ -681,7 +683,7 @@ export default {
 
 				return false;
 			});
-			if (error) return Toast.methods.addToast(error, 8000);
+			if (error) return new Toast({ content: error, timeout: 8000 });
 
 			// Genres
 			error = undefined;
@@ -700,19 +702,20 @@ export default {
 			});
 			if (song.genres.length < 1 || song.genres.length > 16)
 				error = "You must have between 1 and 16 genres.";
-			if (error) return Toast.methods.addToast(error, 8000);
+			if (error) return new Toast({ content: error, timeout: 8000 });
 
 			// Thumbnail
 			if (!validation.isLength(song.thumbnail, 1, 256))
-				return Toast.methods.addToast(
-					"Thumbnail must have between 8 and 256 characters.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Thumbnail must have between 8 and 256 characters.",
+					timeout: 8000
+				});
 			if (this.useHTTPS && song.thumbnail.indexOf("https://") !== 0) {
-				return Toast.methods.addToast(
-					'Thumbnail must start with "https://".',
-					8000
-				);
+				return new Toast({
+					content: 'Thumbnail must start with "https://".',
+					timeout: 8000
+				});
 			}
 
 			if (
@@ -720,10 +723,10 @@ export default {
 				(song.thumbnail.indexOf("http://") !== 0 &&
 					song.thumbnail.indexOf("https://") !== 0)
 			) {
-				return Toast.methods.addToast(
-					'Thumbnail must start with "http://".',
-					8000
-				);
+				return new Toast({
+					content: 'Thumbnail must start with "http://".',
+					timeout: 8000
+				});
 			}
 
 			return this.socket.emit(
@@ -731,7 +734,7 @@ export default {
 				song._id,
 				song,
 				res => {
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 4000 });
 					if (res.status === "success") {
 						this.songs.forEach(originalSong => {
 							const updatedSong = song;
@@ -820,15 +823,15 @@ export default {
 			this.socket.emit("apis.searchDiscogs", query, page, res => {
 				if (res.status === "success") {
 					if (page === 1)
-						Toast.methods.addToast(
-							`Successfully searched. Got ${res.results.length} results.`,
-							4000
-						);
+						new Toast({
+							content: `Successfully searched. Got ${res.results.length} results.`,
+							timeout: 4000
+						});
 					else
-						Toast.methods.addToast(
-							`Successfully got ${res.results.length} more results.`,
-							4000
-						);
+						new Toast({
+							content: `Successfully got ${res.results.length} more results.`,
+							timeout: 4000
+						});
 
 					if (page === 1) {
 						this.discogs.apiResults = [];
@@ -860,7 +863,7 @@ export default {
 
 					this.discogs.page = page;
 					this.discogs.disableLoadMore = false;
-				} else Toast.methods.addToast(res.message, 8000);
+				} else new Toast({ content: res.message, timeout: 8000 });
 			});
 		},
 		loadNextDiscogsPage() {
@@ -969,28 +972,37 @@ export default {
 					.value.toLowerCase()
 					.trim();
 				if (this.editing.song.genres.indexOf(genre) !== -1)
-					return Toast.methods.addToast("Genre already exists", 3000);
+					return new Toast({
+						content: "Genre already exists",
+						timeout: 3000
+					});
 				if (genre) {
 					this.editing.song.genres.push(genre);
 					document.getElementById("new-genre").value = "";
 					return false;
 				}
 
-				return Toast.methods.addToast("Genre cannot be empty", 3000);
+				return new Toast({
+					content: "Genre cannot be empty",
+					timeout: 3000
+				});
 			}
 			if (type === "artists") {
 				const artist = document.getElementById("new-artist").value;
 				if (this.editing.song.artists.indexOf(artist) !== -1)
-					return Toast.methods.addToast(
-						"Artist already exists",
-						3000
-					);
+					return new Toast({
+						content: "Artist already exists",
+						timeout: 3000
+					});
 				if (document.getElementById("new-artist").value !== "") {
 					this.editing.song.artists.push(artist);
 					document.getElementById("new-artist").value = "";
 					return false;
 				}
-				return Toast.methods.addToast("Artist cannot be empty", 3000);
+				return new Toast({
+					content: "Artist cannot be empty",
+					timeout: 3000
+				});
 			}
 
 			return false;
@@ -1223,18 +1235,20 @@ export default {
 						if (this.editing.song.duration > youtubeDuration + 1) {
 							this.video.player.stopVideo();
 							this.video.paused = true;
-							return Toast.methods.addToast(
-								"Video can't play. Specified duration is bigger than the YouTube song duration.",
-								4000
-							);
+							return new Toast({
+								content:
+									"Video can't play. Specified duration is bigger than the YouTube song duration.",
+								timeout: 4000
+							});
 						}
 						if (this.editing.song.duration <= 0) {
 							this.video.player.stopVideo();
 							this.video.paused = true;
-							return Toast.methods.addToast(
-								"Video can't play. Specified duration has to be more than 0 seconds.",
-								4000
-							);
+							return new Toast({
+								content:
+									"Video can't play. Specified duration has to be more than 0 seconds.",
+								timeout: 4000
+							});
 						}
 
 						if (

+ 51 - 34
frontend/components/Modals/EditStation.vue

@@ -294,8 +294,8 @@
 
 <script>
 import { mapState, mapActions } from "vuex";
-import { Toast } from "vue-roaster";
 
+import Toast from "toasters";
 import Modal from "./Modal.vue";
 import io from "../../io";
 import validation from "../../validation";
@@ -403,15 +403,16 @@ export default {
 		updateName() {
 			const { name } = this.editing;
 			if (!validation.isLength(name, 2, 16))
-				return Toast.methods.addToast(
-					"Name must have between 2 and 16 characters.",
-					8000
-				);
+				return new Toast({
+					content: "Name must have between 2 and 16 characters.",
+					timeout: 8000
+				});
 			if (!validation.regex.az09_.test(name))
-				return Toast.methods.addToast(
-					"Invalid name format. Allowed characters: a-z, 0-9 and _.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Invalid name format. Allowed characters: a-z, 0-9 and _.",
+					timeout: 8000
+				});
 
 			return this.socket.emit(
 				"stations.updateName",
@@ -431,22 +432,25 @@ export default {
 							});
 						}
 					}
-					Toast.methods.addToast(res.message, 8000);
+
+					new Toast({ content: res.message, timeout: 8000 });
 				}
 			);
 		},
 		updateDisplayName() {
 			const { displayName } = this.editing;
 			if (!validation.isLength(displayName, 2, 32))
-				return Toast.methods.addToast(
-					"Display name must have between 2 and 32 characters.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Display name must have between 2 and 32 characters.",
+					timeout: 8000
+				});
 			if (!validation.regex.ascii.test(displayName))
-				return Toast.methods.addToast(
-					"Invalid display name format. Only ASCII characters are allowed.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Invalid display name format. Only ASCII characters are allowed.",
+					timeout: 8000
+				});
 
 			return this.socket.emit(
 				"stations.updateDisplayName",
@@ -470,26 +474,28 @@ export default {
 						}
 					}
 
-					return Toast.methods.addToast(res.message, 8000);
+					new Toast({ content: res.message, timeout: 8000 });
 				}
 			);
 		},
 		updateDescription() {
 			const { description } = this.editing;
 			if (!validation.isLength(description, 2, 200))
-				return Toast.methods.addToast(
-					"Description must have between 2 and 200 characters.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Description must have between 2 and 200 characters.",
+					timeout: 8000
+				});
+
 			let characters = description.split("");
 			characters = characters.filter(character => {
 				return character.charCodeAt(0) === 21328;
 			});
 			if (characters.length !== 0)
-				return Toast.methods.addToast(
-					"Invalid description format.",
-					8000
-				);
+				return new Toast({
+					content: "Invalid description format.",
+					timeout: 8000
+				});
 
 			return this.socket.emit(
 				"stations.updateDescription",
@@ -512,9 +518,13 @@ export default {
 							});
 						}
 
-						return Toast.methods.addToast(res.message, 4000);
+						return new Toast({
+							content: res.message,
+							timeout: 4000
+						});
 					}
-					return Toast.methods.addToast(res.message, 8000);
+
+					return new Toast({ content: res.message, timeout: 8000 });
 				}
 			);
 		},
@@ -570,10 +580,14 @@ export default {
 
 							return false;
 						});
-						return Toast.methods.addToast(res.message, 4000);
+
+						return new Toast({
+							content: res.message,
+							timeout: 4000
+						});
 					}
 
-					return Toast.methods.addToast(res.message, 8000);
+					return new Toast({ content: res.message, timeout: 8000 });
 				}
 			);
 		},
@@ -633,10 +647,13 @@ export default {
 						// 	return false;
 						// });
 
-						return Toast.methods.addToast(res.message, 4000);
+						return new Toast({
+							content: res.message,
+							timeout: 4000
+						});
 					}
 
-					return Toast.methods.addToast(res.message, 8000);
+					return new Toast({ content: res.message, timeout: 8000 });
 				}
 			);
 		},
@@ -668,7 +685,7 @@ export default {
 						sector: "station",
 						modal: "editStation"
 					});
-				return Toast.methods.addToast(res.message, 8000);
+				return new Toast({ content: res.message, timeout: 8000 });
 			});
 		},
 		blurGenreInput() {

+ 32 - 27
frontend/components/Modals/EditUser.vue

@@ -92,7 +92,7 @@
 <script>
 import { mapState, mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 import io from "../../io";
 import Modal from "./Modal.vue";
 import validation from "../../validation";
@@ -118,45 +118,49 @@ export default {
 		updateUsername() {
 			const { username } = this.editing;
 			if (!validation.isLength(username, 2, 32))
-				return Toast.methods.addToast(
-					"Username must have between 2 and 32 characters.",
-					8000
-				);
+				return new Toast({
+					content: "Username must have between 2 and 32 characters.",
+					timeout: 8000
+				});
 			if (!validation.regex.custom("a-zA-Z0-9_-").test(username))
-				return Toast.methods.addToast(
-					"Invalid username format. Allowed characters: a-z, A-Z, 0-9, _ and -.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Invalid username format. Allowed characters: a-z, A-Z, 0-9, _ and -.",
+					timeout: 8000
+				});
 
 			return this.socket.emit(
 				`users.updateUsername`,
 				this.editing._id,
 				username,
 				res => {
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 4000 });
 				}
 			);
 		},
 		updateEmail() {
 			const email = this.editing.email.address;
 			if (!validation.isLength(email, 3, 254))
-				return Toast.methods.addToast(
-					"Email must have between 3 and 254 characters.",
-					8000
-				);
+				return new Toast({
+					content: "Email must have between 3 and 254 characters.",
+					timeout: 8000
+				});
 			if (
 				email.indexOf("@") !== email.lastIndexOf("@") ||
 				!validation.regex.emailSimple.test(email) ||
 				!validation.regex.ascii.test(email)
 			)
-				return Toast.methods.addToast("Invalid email format.", 8000);
+				return new Toast({
+					content: "Invalid email format.",
+					timeout: 8000
+				});
 
 			return this.socket.emit(
 				`users.updateEmail`,
 				this.editing._id,
 				email,
 				res => {
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 4000 });
 				}
 			);
 		},
@@ -166,7 +170,7 @@ export default {
 				this.editing._id,
 				this.editing.role,
 				res => {
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 4000 });
 					if (
 						res.status === "success" &&
 						this.editing.role === "default" &&
@@ -179,15 +183,16 @@ export default {
 		banUser() {
 			const { reason } = this.ban;
 			if (!validation.isLength(reason, 1, 64))
-				return Toast.methods.addToast(
-					"Reason must have between 1 and 64 characters.",
-					8000
-				);
+				return new Toast({
+					content: "Reason must have between 1 and 64 characters.",
+					timeout: 8000
+				});
 			if (!validation.regex.ascii.test(reason))
-				return Toast.methods.addToast(
-					"Invalid reason format. Only ascii characters are allowed.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Invalid reason format. Only ascii characters are allowed.",
+					timeout: 8000
+				});
 
 			return this.socket.emit(
 				`users.banUserById`,
@@ -195,13 +200,13 @@ export default {
 				this.ban.reason,
 				this.ban.expiresAt,
 				res => {
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 4000 });
 				}
 			);
 		},
 		removeSessions() {
 			this.socket.emit(`users.removeSessions`, this.editing._id, res => {
-				Toast.methods.addToast(res.message, 4000);
+				new Toast({ content: res.message, timeout: 4000 });
 			});
 		},
 		...mapActions("modals", ["closeModal"])

+ 4 - 2
frontend/components/Modals/Login.vue

@@ -82,7 +82,7 @@
 <script>
 import { mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 
 export default {
 	data() {
@@ -101,7 +101,9 @@ export default {
 				.then(res => {
 					if (res.status === "success") window.location.reload();
 				})
-				.catch(err => Toast.methods.addToast(err.message, 5000));
+				.catch(
+					err => new Toast({ content: err.message, timeout: 5000 })
+				);
 		},
 		resetPassword() {
 			this.closeModal({ sector: "header", modal: "login" });

+ 12 - 10
frontend/components/Modals/Playlists/Create.vue

@@ -23,7 +23,7 @@
 <script>
 import { mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 import Modal from "../Modal.vue";
 import io from "../../../io";
 import validation from "../../../validation";
@@ -47,18 +47,20 @@ export default {
 		createPlaylist() {
 			const { displayName } = this.playlist;
 			if (!validation.isLength(displayName, 2, 32))
-				return Toast.methods.addToast(
-					"Display name must have between 2 and 32 characters.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Display name must have between 2 and 32 characters.",
+					timeout: 8000
+				});
 			if (!validation.regex.ascii.test(displayName))
-				return Toast.methods.addToast(
-					"Invalid display name format. Only ASCII characters are allowed.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Invalid display name format. Only ASCII characters are allowed.",
+					timeout: 8000
+				});
 
 			return this.socket.emit("playlists.create", this.playlist, res => {
-				Toast.methods.addToast(res.message, 3000);
+				new Toast({ content: res.message, timeout: 3000 });
 
 				if (res.status === "success") {
 					this.closeModal({

+ 24 - 21
frontend/components/Modals/Playlists/Edit.vue

@@ -137,7 +137,7 @@
 <script>
 import { mapState, mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 import Modal from "../Modal.vue";
 import io from "../../../io";
 import validation from "../../../validation";
@@ -276,7 +276,7 @@ export default {
 						});
 					}
 				} else if (res.status === "error")
-					Toast.methods.addToast(res.message, 3000);
+					new Toast({ content: res.message, timeout: 3000 });
 			});
 		},
 		addSongToPlaylist(id) {
@@ -285,15 +285,16 @@ export default {
 				id,
 				this.playlist._id,
 				res => {
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 4000 });
 				}
 			);
 		},
 		importPlaylist() {
-			Toast.methods.addToast(
-				"Starting to import your playlist. This can take some time to do.",
-				4000
-			);
+			new Toast({
+				content:
+					"Starting to import your playlist. This can take some time to do.",
+				timeout: 4000
+			});
 			this.socket.emit(
 				"playlists.addSetToPlaylist",
 				this.importQuery,
@@ -301,7 +302,7 @@ export default {
 				res => {
 					if (res.status === "success")
 						this.playlist.songs = res.data;
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 4000 });
 				}
 			);
 		},
@@ -311,35 +312,37 @@ export default {
 				id,
 				this.playlist._id,
 				res => {
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 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
-				);
+				return new Toast({
+					content:
+						"Display name must have between 2 and 32 characters.",
+					timeout: 8000
+				});
 			if (!validation.regex.ascii.test(displayName))
-				return Toast.methods.addToast(
-					"Invalid display name format. Only ASCII characters are allowed.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Invalid display name format. Only ASCII characters are allowed.",
+					timeout: 8000
+				});
 
 			return this.socket.emit(
 				"playlists.updateDisplayName",
 				this.playlist._id,
 				this.playlist.displayName,
 				res => {
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 4000 });
 				}
 			);
 		},
 		removePlaylist() {
 			this.socket.emit("playlists.remove", this.playlist._id, res => {
-				Toast.methods.addToast(res.message, 3000);
+				new Toast({ content: res.message, timeout: 3000 });
 				if (res.status === "success") {
 					this.closeModal();
 				}
@@ -351,7 +354,7 @@ export default {
 				this.playlist._id,
 				songId,
 				res => {
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 4000 });
 				}
 			);
 		},
@@ -361,7 +364,7 @@ export default {
 				this.playlist._id,
 				songId,
 				res => {
-					Toast.methods.addToast(res.message, 4000);
+					new Toast({ content: res.message, timeout: 4000 });
 				}
 			);
 		},

+ 4 - 2
frontend/components/Modals/Register.vue

@@ -84,7 +84,7 @@
 <script>
 import { mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 
 export default {
 	data() {
@@ -136,7 +136,9 @@ export default {
 				.then(res => {
 					if (res.status === "success") window.location.reload();
 				})
-				.catch(err => Toast.methods.addToast(err.message, 5000));
+				.catch(
+					err => new Toast({ content: err.message, timeout: 5000 })
+				);
 		},
 		githubRedirect() {
 			localStorage.setItem("github_redirect", this.$route.path);

+ 2 - 2
frontend/components/Modals/Report.vue

@@ -151,7 +151,7 @@
 <script>
 import { mapState, mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 import Modal from "./Modal.vue";
 import io from "../../io";
 
@@ -222,7 +222,7 @@ export default {
 		create() {
 			console.log(this.report);
 			this.socket.emit("reports.create", this.report, res => {
-				Toast.methods.addToast(res.message, 4000);
+				new Toast({ content: res.message, timeout: 4000 });
 				if (res.status === "success")
 					this.closeModal({
 						sector: "station",

+ 6 - 3
frontend/components/Sidebars/Playlist.vue

@@ -48,7 +48,7 @@
 <script>
 import { mapState, mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 import io from "../../io";
 
 export default {
@@ -77,8 +77,11 @@ export default {
 				id,
 				res => {
 					if (res.status === "failure")
-						return Toast.methods.addToast(res.message, 8000);
-					return Toast.methods.addToast(res.message, 4000);
+						return new Toast({
+							content: res.message,
+							timeout: 8000
+						});
+					return new Toast({ content: res.message, timeout: 4000 });
 				}
 			);
 		},

+ 7 - 6
frontend/components/Sidebars/SongsList.vue

@@ -128,7 +128,7 @@
 <script>
 import { mapState, mapActions } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 
 import UserIdToUsername from "../UserIdToUsername.vue";
 
@@ -161,11 +161,12 @@ export default {
 				songId,
 				res => {
 					if (res.status === "success") {
-						Toast.methods.addToast(
-							"Successfully removed song from the queue.",
-							4000
-						);
-					} else Toast.methods.addToast(res.message, 8000);
+						new Toast({
+							content:
+								"Successfully removed song from the queue.",
+							timeout: 4000
+						});
+					} else new Toast({ content: res.message, timeout: 8000 });
 				}
 			);
 		},

+ 65 - 48
frontend/components/Station/Station.vue

@@ -422,7 +422,7 @@
 
 <script>
 import { mapState, mapActions } from "vuex";
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 
 import StationHeader from "./StationHeader.vue";
 
@@ -493,11 +493,12 @@ export default {
 				songId,
 				res => {
 					if (res.status === "success") {
-						Toast.methods.addToast(
-							"Successfully removed song from the queue.",
-							4000
-						);
-					} else Toast.methods.addToast(res.message, 8000);
+						new Toast({
+							content:
+								"Successfully removed song from the queue.",
+							timeout: 4000
+						});
+					} else new Toast({ content: res.message, timeout: 8000 });
 				}
 			);
 		},
@@ -749,11 +750,11 @@ export default {
 		toggleLock() {
 			window.socket.emit("stations.toggleLock", this.station._id, res => {
 				if (res.status === "success") {
-					Toast.methods.addToast(
-						"Successfully toggled the queue lock.",
-						4000
-					);
-				} else Toast.methods.addToast(res.message, 8000);
+					new Toast({
+						content: "Successfully toggled the queue lock.",
+						timeout: 4000
+					});
+				} else new Toast({ content: res.message, timeout: 8000 });
 			});
 		},
 		changeVolume() {
@@ -790,45 +791,58 @@ export default {
 		skipStation() {
 			this.socket.emit("stations.forceSkip", this.station._id, data => {
 				if (data.status !== "success")
-					Toast.methods.addToast(`Error: ${data.message}`, 8000);
+					new Toast({
+						content: `Error: ${data.message}`,
+						timeout: 8000
+					});
 				else
-					Toast.methods.addToast(
-						"Successfully skipped the station's current song.",
-						4000
-					);
+					new Toast({
+						content:
+							"Successfully skipped the station's current song.",
+						timeout: 4000
+					});
 			});
 		},
 		voteSkipStation() {
 			this.socket.emit("stations.voteSkip", this.station._id, data => {
 				if (data.status !== "success")
-					Toast.methods.addToast(`Error: ${data.message}`, 8000);
+					new Toast({
+						content: `Error: ${data.message}`,
+						timeout: 8000
+					});
 				else
-					Toast.methods.addToast(
-						"Successfully voted to skip the current song.",
-						4000
-					);
+					new Toast({
+						content: "Successfully voted to skip the current song.",
+						timeout: 4000
+					});
 			});
 		},
 		resumeStation() {
 			this.socket.emit("stations.resume", this.station._id, data => {
 				if (data.status !== "success")
-					Toast.methods.addToast(`Error: ${data.message}`, 8000);
+					new Toast({
+						content: `Error: ${data.message}`,
+						timeout: 8000
+					});
 				else
-					Toast.methods.addToast(
-						"Successfully resumed the station.",
-						4000
-					);
+					new Toast({
+						content: "Successfully resumed the station.",
+						timeout: 4000
+					});
 			});
 		},
 		pauseStation() {
 			this.socket.emit("stations.pause", this.station._id, data => {
 				if (data.status !== "success")
-					Toast.methods.addToast(`Error: ${data.message}`, 8000);
+					new Toast({
+						content: `Error: ${data.message}`,
+						timeout: 8000
+					});
 				else
-					Toast.methods.addToast(
-						"Successfully paused the station.",
-						4000
-					);
+					new Toast({
+						content: "Successfully paused the station.",
+						timeout: 4000
+					});
 			});
 		},
 		toggleMute() {
@@ -866,10 +880,10 @@ export default {
 					this.currentSong.songId,
 					data => {
 						if (data.status !== "success")
-							Toast.methods.addToast(
-								`Error: ${data.message}`,
-								8000
-							);
+							new Toast({
+								content: `Error: ${data.message}`,
+								timeout: 8000
+							});
 					}
 				);
 			else
@@ -878,10 +892,10 @@ export default {
 					this.currentSong.songId,
 					data => {
 						if (data.status !== "success")
-							Toast.methods.addToast(
-								`Error: ${data.message}`,
-								8000
-							);
+							new Toast({
+								content: `Error: ${data.message}`,
+								timeout: 8000
+							});
 					}
 				);
 		},
@@ -892,10 +906,10 @@ export default {
 					this.currentSong.songId,
 					data => {
 						if (data.status !== "success")
-							Toast.methods.addToast(
-								`Error: ${data.message}`,
-								8000
-							);
+							new Toast({
+								content: `Error: ${data.message}`,
+								timeout: 8000
+							});
 					}
 				);
 
@@ -904,7 +918,10 @@ export default {
 				this.currentSong.songId,
 				data => {
 					if (data.status !== "success")
-						Toast.methods.addToast(`Error: ${data.message}`, 8000);
+						new Toast({
+							content: `Error: ${data.message}`,
+							timeout: 8000
+						});
 				}
 			);
 		},
@@ -945,10 +962,10 @@ export default {
 										}
 									);
 								} else {
-									Toast.methods.addToast(
-										`Top song in playlist was too long to be added.`,
-										3000
-									);
+									new Toast({
+										content: `Top song in playlist was too long to be added.`,
+										timeout: 3000
+									});
 									this.socket.emit(
 										"playlists.moveSongToBottom",
 										this.privatePlaylistQueueSelected,

+ 16 - 7
frontend/components/User/ResetPassword.vue

@@ -69,7 +69,7 @@
 </template>
 
 <script>
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 
 import MainHeader from "../MainHeader.vue";
 import MainFooter from "../MainFooter.vue";
@@ -94,12 +94,15 @@ export default {
 	methods: {
 		submitEmail() {
 			if (!this.email)
-				return Toast.methods.addToast("Email cannot be empty", 8000);
+				return new Toast({
+					content: "Email cannot be empty",
+					timeout: 8000
+				});
 			return this.socket.emit(
 				"users.requestPasswordReset",
 				this.email,
 				res => {
-					Toast.methods.addToast(res.message, 8000);
+					new Toast({ content: res.message, timeout: 8000 });
 					if (res.status === "success") {
 						this.step = 2;
 					}
@@ -108,12 +111,15 @@ export default {
 		},
 		verifyCode() {
 			if (!this.code)
-				return Toast.methods.addToast("Code cannot be empty", 8000);
+				return new Toast({
+					content: "Code cannot be empty",
+					timeout: 8000
+				});
 			return this.socket.emit(
 				"users.verifyPasswordResetCode",
 				this.code,
 				res => {
-					Toast.methods.addToast(res.message, 8000);
+					new Toast({ content: res.message, timeout: 8000 });
 					if (res.status === "success") {
 						this.step = 3;
 					}
@@ -122,13 +128,16 @@ export default {
 		},
 		changePassword() {
 			if (!this.newPassword)
-				return Toast.methods.addToast("Password cannot be empty", 8000);
+				return new Toast({
+					content: "Password cannot be empty",
+					timeout: 8000
+				});
 			return this.socket.emit(
 				"users.changePasswordWithResetCode",
 				this.code,
 				this.newPassword,
 				res => {
-					Toast.methods.addToast(res.message, 8000);
+					new Toast({ content: res.message, timeout: 8000 });
 					if (res.status === "success") {
 						this.$router.go("/login");
 					}

+ 65 - 56
frontend/components/User/Settings.vue

@@ -149,7 +149,7 @@
 <script>
 import { mapState } from "vuex";
 
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 
 import MainHeader from "../MainHeader.vue";
 import MainFooter from "../MainFooter.vue";
@@ -187,10 +187,10 @@ export default {
 					this.password = this.user.password;
 					this.github = this.user.github;
 				} else {
-					Toast.methods.addToast(
-						"Your are currently not signed in",
-						3000
-					);
+					new Toast({
+						content: "Your are currently not signed in",
+						timeout: 3000
+					});
 				}
 			});
 			this.socket.on("event:user.linkPassword", () => {
@@ -211,15 +211,18 @@ export default {
 		changeEmail() {
 			const email = this.user.email.address;
 			if (!validation.isLength(email, 3, 254))
-				return Toast.methods.addToast(
-					"Email must have between 3 and 254 characters.",
-					8000
-				);
+				return new Toast({
+					content: "Email must have between 3 and 254 characters.",
+					timeout: 8000
+				});
 			if (
 				email.indexOf("@") !== email.lastIndexOf("@") ||
 				!validation.regex.emailSimple.test(email)
 			)
-				return Toast.methods.addToast("Invalid email format.", 8000);
+				return new Toast({
+					content: "Invalid email format.",
+					timeout: 8000
+				});
 
 			return this.socket.emit(
 				"users.updateEmail",
@@ -227,27 +230,28 @@ export default {
 				email,
 				res => {
 					if (res.status !== "success")
-						Toast.methods.addToast(res.message, 8000);
+						new Toast({ content: res.message, timeout: 8000 });
 					else
-						Toast.methods.addToast(
-							"Successfully changed email address",
-							4000
-						);
+						new Toast({
+							content: "Successfully changed email address",
+							timeout: 4000
+						});
 				}
 			);
 		},
 		changeUsername() {
 			const { username } = this.user;
 			if (!validation.isLength(username, 2, 32))
-				return Toast.methods.addToast(
-					"Username must have between 2 and 32 characters.",
-					8000
-				);
+				return new Toast({
+					content: "Username must have between 2 and 32 characters.",
+					timeout: 8000
+				});
 			if (!validation.regex.azAZ09_.test(username))
-				return Toast.methods.addToast(
-					"Invalid username format. Allowed characters: a-z, A-Z, 0-9 and _.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Invalid username format. Allowed characters: a-z, A-Z, 0-9 and _.",
+					timeout: 8000
+				});
 
 			return this.socket.emit(
 				"users.updateUsername",
@@ -255,45 +259,46 @@ export default {
 				username,
 				res => {
 					if (res.status !== "success")
-						Toast.methods.addToast(res.message, 8000);
+						new Toast({ content: res.message, timeout: 8000 });
 					else
-						Toast.methods.addToast(
-							"Successfully changed username",
-							4000
-						);
+						new Toast({
+							content: "Successfully changed username",
+							timeout: 4000
+						});
 				}
 			);
 		},
 		changePassword() {
 			const { newPassword } = this;
 			if (!validation.isLength(newPassword, 6, 200))
-				return Toast.methods.addToast(
-					"Password must have between 6 and 200 characters.",
-					8000
-				);
+				return new Toast({
+					content: "Password must have between 6 and 200 characters.",
+					timeout: 8000
+				});
 			if (!validation.regex.password.test(newPassword))
-				return Toast.methods.addToast(
-					"Invalid password format. Must have one lowercase letter, one uppercase letter, one number and one special character.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Invalid password format. Must have one lowercase letter, one uppercase letter, one number and one special character.",
+					timeout: 8000
+				});
 
 			return this.socket.emit(
 				"users.updatePassword",
 				newPassword,
 				res => {
 					if (res.status !== "success")
-						Toast.methods.addToast(res.message, 8000);
+						new Toast({ content: res.message, timeout: 8000 });
 					else
-						Toast.methods.addToast(
-							"Successfully changed password",
-							4000
-						);
+						new Toast({
+							content: "Successfully changed password",
+							timeout: 4000
+						});
 				}
 			);
 		},
 		requestPassword() {
 			return this.socket.emit("users.requestPassword", res => {
-				Toast.methods.addToast(res.message, 8000);
+				new Toast({ content: res.message, timeout: 8000 });
 				if (res.status === "success") {
 					this.passwordStep = 2;
 				}
@@ -301,12 +306,15 @@ export default {
 		},
 		verifyCode() {
 			if (!this.passwordCode)
-				return Toast.methods.addToast("Code cannot be empty", 8000);
+				return new Toast({
+					content: "Code cannot be empty",
+					timeout: 8000
+				});
 			return this.socket.emit(
 				"users.verifyPasswordCode",
 				this.passwordCode,
 				res => {
-					Toast.methods.addToast(res.message, 8000);
+					new Toast({ content: res.message, timeout: 8000 });
 					if (res.status === "success") {
 						this.passwordStep = 3;
 					}
@@ -316,38 +324,39 @@ export default {
 		setPassword() {
 			const newPassword = this.setNewPassword;
 			if (!validation.isLength(newPassword, 6, 200))
-				return Toast.methods.addToast(
-					"Password must have between 6 and 200 characters.",
-					8000
-				);
+				return new Toast({
+					content: "Password must have between 6 and 200 characters.",
+					timeout: 8000
+				});
 			if (!validation.regex.password.test(newPassword))
-				return Toast.methods.addToast(
-					"Invalid password format. Must have one lowercase letter, one uppercase letter, one number and one special character.",
-					8000
-				);
+				return new Toast({
+					content:
+						"Invalid password format. Must have one lowercase letter, one uppercase letter, one number and one special character.",
+					timeout: 8000
+				});
 
 			return this.socket.emit(
 				"users.changePasswordWithCode",
 				this.passwordCode,
 				newPassword,
 				res => {
-					Toast.methods.addToast(res.message, 8000);
+					new Toast({ content: res.message, timeout: 8000 });
 				}
 			);
 		},
 		unlinkPassword() {
 			this.socket.emit("users.unlinkPassword", res => {
-				Toast.methods.addToast(res.message, 8000);
+				new Toast({ content: res.message, timeout: 8000 });
 			});
 		},
 		unlinkGitHub() {
 			this.socket.emit("users.unlinkGitHub", res => {
-				Toast.methods.addToast(res.message, 8000);
+				new Toast({ content: res.message, timeout: 8000 });
 			});
 		},
 		removeSessions() {
 			this.socket.emit(`users.removeSessions`, this.userId, res => {
-				Toast.methods.addToast(res.message, 4000);
+				new Toast({ content: res.message, timeout: 4000 });
 			});
 		}
 	}

+ 6 - 6
frontend/components/User/Show.vue

@@ -64,7 +64,7 @@
 
 <script>
 import { mapState } from "vuex";
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 import { format, parseISO } from "date-fns";
 
 import MainHeader from "../MainHeader.vue";
@@ -111,12 +111,12 @@ export default {
 				newRank === "admin" ? "admin" : "default",
 				res => {
 					if (res.status === "error")
-						Toast.methods.addToast(res.message, 2000);
+						new Toast({ content: res.message, timeout: 2000 });
 					else this.user.role = newRank;
-					Toast.methods.addToast(
-						`User ${this.$route.params.username}'s rank has been changed to: ${newRank}`,
-						2000
-					);
+					new Toast({
+						content: `User ${this.$route.params.username}'s rank has been changed to: ${newRank}`,
+						timeout: 2000
+					});
 				}
 			);
 		}

+ 11 - 11
frontend/components/pages/Home.vue

@@ -124,7 +124,7 @@
 
 <script>
 import { mapState, mapActions } from "vuex";
-import { Toast } from "vue-roaster";
+import Toast from "toasters";
 
 import MainHeader from "../MainHeader.vue";
 import MainFooter from "../MainFooter.vue";
@@ -246,22 +246,22 @@ export default {
 			event.preventDefault();
 			this.socket.emit("stations.favoriteStation", station._id, res => {
 				if (res.status === "success") {
-					Toast.methods.addToast(
-						"Successfully favorited station.",
-						4000
-					);
-				} else Toast.methods.addToast(res.message, 8000);
+					new Toast({
+						content: "Successfully favorited station.",
+						timeout: 4000
+					});
+				} else new Toast({ content: res.message, timeout: 8000 });
 			});
 		},
 		unfavoriteStation(event, station) {
 			event.preventDefault();
 			this.socket.emit("stations.unfavoriteStation", station._id, res => {
 				if (res.status === "success") {
-					Toast.methods.addToast(
-						"Successfully unfavorited station.",
-						4000
-					);
-				} else Toast.methods.addToast(res.message, 8000);
+					new Toast({
+						content: "Successfully unfavorited station.",
+						timeout: 4000
+					});
+				} else new Toast({ content: res.message, timeout: 8000 });
 			});
 		},
 		...mapActions("modals", ["openModal"])

+ 1 - 0
frontend/dist/index.tpl.html

@@ -43,5 +43,6 @@
 </head>
 <body>
 	<div id="root"></div>
+	<div id="toasts-container"></div>
 </body>
 </html>

+ 1 - 1
frontend/package.json

@@ -50,9 +50,9 @@
     "date-fns": "^2.0.1",
     "eslint-config-airbnb-base": "^13.2.0",
     "html-webpack-plugin": "^3.2.0",
+    "toasters": "^2.0.0",
     "vue": "^2.6.10",
     "vue-loader": "^15.7.0",
-    "vue-roaster": "^1.1.1",
     "vue-router": "^3.0.7",
     "vuex": "^3.1.1",
     "webpack-md5-hash": "0.0.6",

+ 13 - 146
yarn.lock

@@ -1789,11 +1789,6 @@ acorn-walk@^6.1.1:
   resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c"
   integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==
 
-acorn@^5.2.1:
-  version "5.7.3"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279"
-  integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==
-
 acorn@^6.0.2, acorn@^6.0.7, acorn@^6.2.1:
   version "6.2.1"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.1.tgz#3ed8422d6dec09e6121cc7a843ca86a330a86b51"
@@ -2083,11 +2078,6 @@ assign-symbols@^1.0.0:
   resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
   integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
 
-ast-types@0.9.6:
-  version "0.9.6"
-  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9"
-  integrity sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=
-
 ast-types@0.x.x:
   version "0.13.2"
   resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.2.tgz#df39b677a911a83f3a049644fb74fdded23cea48"
@@ -2191,14 +2181,6 @@ babel-plugin-dynamic-import-node@^2.3.0:
   dependencies:
     object.assign "^4.1.0"
 
-babel-runtime@^6.0.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
-  integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
-  dependencies:
-    core-js "^2.4.0"
-    regenerator-runtime "^0.11.0"
-
 backo2@1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
@@ -2209,11 +2191,6 @@ balanced-match@^1.0.0:
   resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
   integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
 
-base62@^1.1.0:
-  version "1.2.8"
-  resolved "https://registry.yarnpkg.com/base62/-/base62-1.2.8.tgz#1264cb0fb848d875792877479dbe8bae6bae3428"
-  integrity sha512-V6YHUbjLxN1ymqNLb1DPHoU1CpfdL7d2YTIp5W3U4hhoG4hhxNmsFDs66M9EXxBiSEke5Bt5dwdfMwwZF70iLA==
-
 base64-arraybuffer@0.1.5:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
@@ -2948,7 +2925,7 @@ commander@2.17.x:
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
   integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
 
-commander@^2.18.0, commander@^2.20.0, commander@^2.5.0, commander@~2.20.0:
+commander@^2.18.0, commander@^2.20.0, commander@~2.20.0:
   version "2.20.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
   integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
@@ -2963,21 +2940,6 @@ commondir@^1.0.1:
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
   integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
 
-commoner@^0.10.1:
-  version "0.10.8"
-  resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5"
-  integrity sha1-NPw2cs0kOT6LtH5wyqApOBH08sU=
-  dependencies:
-    commander "^2.5.0"
-    detective "^4.3.1"
-    glob "^5.0.15"
-    graceful-fs "^4.1.2"
-    iconv-lite "^0.4.5"
-    mkdirp "^0.5.0"
-    private "^0.1.6"
-    q "^1.1.2"
-    recast "^0.11.17"
-
 compare-func@^1.3.1:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-1.3.2.tgz#99dd0ba457e1f9bc722b12c08ec33eeab31fa648"
@@ -3283,11 +3245,6 @@ core-js-pure@3.1.4:
   resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.1.4.tgz#5fa17dc77002a169a3566cc48dc774d2e13e3769"
   integrity sha512-uJ4Z7iPNwiu1foygbcZYJsJs1jiXrTTCvxfLDXNhI/I+NHbSIEyr548y4fcsCEyWY0XgfAG/qqaunJ1SThHenA==
 
-core-js@^2.4.0:
-  version "2.6.9"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
-  integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
-
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -3632,11 +3589,6 @@ define-property@^2.0.2:
     is-descriptor "^1.0.2"
     isobject "^3.0.1"
 
-defined@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
-  integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
-
 degenerator@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095"
@@ -3712,14 +3664,6 @@ detect-node@^2.0.4:
   resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
   integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
 
-detective@^4.3.1:
-  version "4.7.1"
-  resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e"
-  integrity sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==
-  dependencies:
-    acorn "^5.2.1"
-    defined "^1.0.0"
-
 dezalgo@^1.0.0:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456"
@@ -4028,14 +3972,6 @@ env-paths@^1.0.0:
   resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0"
   integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=
 
-envify@^3.4.0:
-  version "3.4.1"
-  resolved "https://registry.yarnpkg.com/envify/-/envify-3.4.1.tgz#d7122329e8df1688ba771b12501917c9ce5cbce8"
-  integrity sha1-1xIjKejfFoi6dxsSUBkXyc5cvOg=
-  dependencies:
-    jstransform "^11.0.3"
-    through "~2.3.4"
-
 err-code@^1.0.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960"
@@ -4281,12 +4217,7 @@ espree@^6.0.0:
     acorn-jsx "^5.0.0"
     eslint-visitor-keys "^1.0.0"
 
-esprima-fb@^15001.1.0-dev-harmony-fb:
-  version "15001.1.0-dev-harmony-fb"
-  resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901"
-  integrity sha1-MKlHMDxrjV6VW+4rmbHSMyBqaQE=
-
-esprima@3.x.x, esprima@^3.1.3, esprima@~3.1.0:
+esprima@3.x.x, esprima@^3.1.3:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
   integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=
@@ -4989,17 +4920,6 @@ glob-to-regexp@^0.3.0:
   resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
   integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
 
-glob@^5.0.15:
-  version "5.0.15"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
-  integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=
-  dependencies:
-    inflight "^1.0.4"
-    inherits "2"
-    minimatch "2 || 3"
-    once "^1.3.0"
-    path-is-absolute "^1.0.0"
-
 glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1:
   version "7.1.4"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
@@ -5481,7 +5401,7 @@ husky@^3.0.4:
     run-node "^1.0.0"
     slash "^3.0.0"
 
-iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@^0.4.5, iconv-lite@~0.4.13:
+iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
   integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -6031,16 +5951,6 @@ isstream@~0.1.2:
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
   integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
 
-jquery-ui@^1.12.1:
-  version "1.12.1"
-  resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51"
-  integrity sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE=
-
-jquery@^3.1.1:
-  version "3.4.1"
-  resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2"
-  integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==
-
 js-base64@^2.1.8:
   version "2.5.1"
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121"
@@ -6160,17 +6070,6 @@ jsprim@^1.2.2:
     json-schema "0.2.3"
     verror "1.10.0"
 
-jstransform@^11.0.3:
-  version "11.0.3"
-  resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-11.0.3.tgz#09a78993e0ae4d4ef4487f6155a91f6190cb4223"
-  integrity sha1-CaeJk+CuTU70SH9hVakfYZDLQiM=
-  dependencies:
-    base62 "^1.1.0"
-    commoner "^0.10.1"
-    esprima-fb "^15001.1.0-dev-harmony-fb"
-    object-assign "^2.0.0"
-    source-map "^0.4.2"
-
 jszip@^3.1.5:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.2.2.tgz#b143816df7e106a9597a94c77493385adca5bd1d"
@@ -6793,7 +6692,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
   integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
 
-"minimatch@2 || 3", minimatch@^3.0.4, minimatch@~3.0.2:
+minimatch@^3.0.4, minimatch@~3.0.2:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
   integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@@ -7376,11 +7275,6 @@ oauth@^0.9.15:
   resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
   integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
 
-object-assign@^2.0.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa"
-  integrity sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=
-
 object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -8155,7 +8049,7 @@ prism-media@^0.0.3:
   resolved "https://registry.yarnpkg.com/prism-media/-/prism-media-0.0.3.tgz#8842d4fae804f099d3b48a9a38e3c2bab6f4855b"
   integrity sha512-c9KkNifSMU/iXT8FFTaBwBMr+rdVcN+H/uNv1o+CuFeTThNZNTOrQ+RgXA1yL/DeLk098duAeRPP3QNPNbhxYQ==
 
-private@^0.1.6, private@~0.1.5:
+private@^0.1.6:
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
   integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
@@ -8320,7 +8214,7 @@ punycode@^2.1.0:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
 
-q@^1.1.2, q@^1.5.1:
+q@^1.5.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
   integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
@@ -8552,16 +8446,6 @@ readdirp@^2.2.1:
     micromatch "^3.1.10"
     readable-stream "^2.0.2"
 
-recast@^0.11.17:
-  version "0.11.23"
-  resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3"
-  integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=
-  dependencies:
-    ast-types "0.9.6"
-    esprima "~3.1.0"
-    private "~0.1.5"
-    source-map "~0.5.0"
-
 redent@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
@@ -8609,11 +8493,6 @@ regenerate@^1.4.0:
   resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
   integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==
 
-regenerator-runtime@^0.11.0:
-  version "0.11.1"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
-  integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-
 regenerator-runtime@^0.13.2:
   version "0.13.3"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
@@ -9614,7 +9493,7 @@ source-map@^0.4.2:
   dependencies:
     amdefine ">=0.0.4"
 
-source-map@^0.5.0, source-map@^0.5.6, source-map@~0.5.0:
+source-map@^0.5.0, source-map@^0.5.6:
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
   integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
@@ -10061,7 +9940,7 @@ through2@^3.0.0:
   dependencies:
     readable-stream "2 || 3"
 
-through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3.4:
+through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6:
   version "2.3.8"
   resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
   integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
@@ -10147,6 +10026,11 @@ to-regex@^3.0.1, to-regex@^3.0.2:
     regex-not "^1.0.2"
     safe-regex "^1.1.0"
 
+toasters@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/toasters/-/toasters-2.0.0.tgz#b104ca7c638f02bba362eb5029d460a602e45681"
+  integrity sha512-fm+LQoos6/ouDJbgRn+Wg6kzW5iAzRhApBI8PXgbSvksO+wpgmhrOm7+XCgHU5aLGwosHCyTUEo4lCEHBTNDNA==
+
 toidentifier@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
@@ -10610,16 +10494,6 @@ vue-loader@^15.7.0:
     vue-hot-reload-api "^2.3.0"
     vue-style-loader "^4.1.0"
 
-vue-roaster@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/vue-roaster/-/vue-roaster-1.1.1.tgz#4718300137862e621e8d7f9130c6dacb5989ca1a"
-  integrity sha1-RxgwATeGLmIejX+RMMbay1mJyho=
-  dependencies:
-    babel-runtime "^6.0.0"
-    jquery "^3.1.1"
-    jquery-ui "^1.12.1"
-    vue "^1.0.0"
-
 vue-router@^3.0.7:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.1.1.tgz#0893c29548ba2dbe35ed104dcd1aa06743aa0ead"
@@ -10646,13 +10520,6 @@ vue-template-es2015-compiler@^1.9.0:
   resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
   integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
 
-vue@^1.0.0:
-  version "1.0.28"
-  resolved "https://registry.yarnpkg.com/vue/-/vue-1.0.28.tgz#ed2ff07b200bde15c87a90ef8727ceea7d38567d"
-  integrity sha1-7S/weyAL3hXIepDvhyfO6n04Vn0=
-  dependencies:
-    envify "^3.4.0"
-
 vue@^2.6.10:
   version "2.6.10"
   resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"