diff --git a/public/css/progressbar.css b/public/css/progressbar.css
index 38fb95c..5fff18a 100644
--- a/public/css/progressbar.css
+++ b/public/css/progressbar.css
@@ -108,3 +108,30 @@
right: -8%;
}
}
+
+.toast-icon{
+ display: inline-block;
+ margin-right: 8px;
+}
+
+.circle-loader {
+ display: inline-block;
+ border: 2px solid var(--accent-color);
+ border-radius: 50%;
+ border-bottom: 2px solid var(--secondary-background);
+ width: 16px;
+ height: 16px;
+ -webkit-animation: spin 1s linear infinite; /* Safari */
+ animation: spin 1s linear infinite;
+}
+
+/* Safari */
+@-webkit-keyframes spin {
+ 0% { -webkit-transform: rotate(0deg); }
+ 100% { -webkit-transform: rotate(360deg); }
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
diff --git a/public/css/style.css b/public/css/style.css
index d5a3d5b..ae25c12 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -283,3 +283,10 @@ span.tag {
.clickable{
cursor: pointer;
}
+
+.toastify{
+ display: flex;
+ align-items:center;
+ box-shadow: 0 3px 6px -1px rgba(0, 0, 0, 0.12), 0 10px 36px -4px rgba(0, 0, 0, 0.3);
+ background: #333333;
+}
diff --git a/public/css/toastify.css b/public/css/toastify.css
new file mode 100644
index 0000000..751d488
--- /dev/null
+++ b/public/css/toastify.css
@@ -0,0 +1,78 @@
+/*!
+ * Toastify js 1.7.0
+ * https://github.com/apvarun/toastify-js
+ * @license MIT licensed
+ *
+ * Copyright (C) 2018 Varun A P
+ */
+
+.toastify {
+ padding: 12px 20px;
+ color: #ffffff;
+ display: inline-block;
+ box-shadow: 0 3px 6px -1px rgba(0, 0, 0, 0.12), 0 10px 36px -4px rgba(77, 96, 232, 0.3);
+ background: -webkit-linear-gradient(315deg, #73a5ff, #5477f5);
+ background: linear-gradient(135deg, #73a5ff, #5477f5);
+ position: fixed;
+ opacity: 0;
+ transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);
+ border-radius: 2px;
+ cursor: pointer;
+ text-decoration: none;
+ max-width: calc(50% - 20px);
+ z-index: 2147483647;
+}
+
+.toastify.on {
+ opacity: 1;
+}
+
+.toast-close {
+ opacity: 0.4;
+ padding: 0 5px;
+}
+
+.toastify-right {
+ right: 15px;
+}
+
+.toastify-left {
+ left: 64px;
+}
+
+.toastify-top {
+ top: -150px;
+}
+
+.toastify-bottom {
+ bottom: -150px;
+}
+
+.toastify-rounded {
+ border-radius: 25px;
+}
+
+.toastify-avatar {
+ width: 1.5em;
+ height: 1.5em;
+ margin: 0 5px;
+ border-radius: 2px;
+}
+
+.toastify-center {
+ margin-left: auto;
+ margin-right: auto;
+ left: 0;
+ right: 0;
+ max-width: fit-content;
+}
+
+@media only screen and (max-width: 360px) {
+ .toastify-right, .toastify-left {
+ margin-left: auto;
+ margin-right: auto;
+ left: 0;
+ right: 0;
+ max-width: fit-content;
+ }
+}
diff --git a/public/index.html b/public/index.html
index 0698798..e1b9111 100644
--- a/public/index.html
+++ b/public/index.html
@@ -3,11 +3,12 @@
deemix
-
-
+
+
+
+
diff --git a/public/js/downloadList.js b/public/js/downloadList.js
index 2c799fa..6468507 100644
--- a/public/js/downloadList.js
+++ b/public/js/downloadList.js
@@ -24,6 +24,12 @@ socket.on("startDownload", function(uuid){
$('#bar_' + uuid).removeClass('indeterminate').addClass('determinate')
})
+socket.on("finishDownload", function(uuid){
+ console.log(uuid+" finished downloading")
+ toast(`${queueList[uuid].title} finished downloading.`)
+ $('#bar_' + uuid).css('width', '100%')
+})
+
socket.on("updateQueue", function(update){
if (update.uuid && queue.indexOf(update.uuid) > -1){
console.log(update)
diff --git a/public/js/init.js b/public/js/init.js
index e85781b..3306134 100644
--- a/public/js/init.js
+++ b/public/js/init.js
@@ -3,3 +3,51 @@ const socket = io.connect(window.location.href)
localStorage = window.localStorage;
search_selected = ""
main_selected=""
+toastsWithId = {}
+
+function toast(msg, icon=null, dismiss=true, id=null){
+ if (id && $(`div.toastify[toast_id=${id}]`).length)
+ return
+ if (icon == null)
+ icon = ""
+ else if (icon=='loading')
+ icon = ``
+ else
+ icon = `${icon}`
+ toastObj = Toastify({
+ text: `${icon}${msg}`,
+ duration: dismiss ? 3000 : 0,
+ gravity: 'bottom',
+ position: 'left'
+ }).showToast()
+ if (id){
+ toastsWithId[id] = toastObj
+ $(toastObj.toastElement).attr('toast_id', id)
+ }
+}
+
+socket.on("toast", (data)=>{
+ toast(data.msg, data.icon || null, data.dismiss !== undefined ? data.dismiss : true, data.id || null)
+})
+
+socket.on("updateToast", (data)=>{
+ if (toastsWithId[data.id]){
+ toastObj = toastsWithId[data.id]
+ toastDOM = $(`div.toastify[toast_id=${data.id}]`)
+ if (data.msg){
+ toastDOM.find(".toast-message").html(data.msg)
+ }
+ if (data.icon){
+ if (data.icon=='loading')
+ icon = ``
+ else
+ icon = `${data.icon}`
+ toastDOM.find(".toast-icon").html(icon)
+ }
+ if (data.dismiss !== null && data.dismiss){
+ setTimeout(function(){ toastObj.hideToast() }, 3000);
+ }
+ }else{
+ toast(data.msg, data.icon || null, data.dismiss !== null ? data.dismiss : true, data.id || null)
+ }
+})
diff --git a/public/js/toastify.js b/public/js/toastify.js
new file mode 100644
index 0000000..8bf2e5e
--- /dev/null
+++ b/public/js/toastify.js
@@ -0,0 +1,351 @@
+/*!
+ * Toastify js 1.7.0
+ * https://github.com/apvarun/toastify-js
+ * @license MIT licensed
+ *
+ * Copyright (C) 2018 Varun A P
+ */
+(function(root, factory) {
+ if (typeof module === "object" && module.exports) {
+ module.exports = factory();
+ } else {
+ root.Toastify = factory();
+ }
+})(this, function(global) {
+ // Object initialization
+ var Toastify = function(options) {
+ // Returning a new init object
+ return new Toastify.lib.init(options);
+ },
+ // Library version
+ version = "1.7.0";
+
+ // Defining the prototype of the object
+ Toastify.lib = Toastify.prototype = {
+ toastify: version,
+
+ constructor: Toastify,
+
+ // Initializing the object with required parameters
+ init: function(options) {
+ // Verifying and validating the input object
+ if (!options) {
+ options = {};
+ }
+
+ // Creating the options object
+ this.options = {};
+
+ this.toastElement = null;
+
+ // Validating the options
+ this.options.text = options.text || "Hi there!"; // Display message
+ this.options.duration = options.duration === 0 ? 0 : options.duration || 3000; // Display duration
+ this.options.selector = options.selector; // Parent selector
+ this.options.callback = options.callback || function() {}; // Callback after display
+ this.options.destination = options.destination; // On-click destination
+ this.options.newWindow = options.newWindow || false; // Open destination in new window
+ this.options.close = options.close || false; // Show toast close icon
+ this.options.gravity = options.gravity === "bottom" ? "toastify-bottom" : "toastify-top"; // toast position - top or bottom
+ this.options.positionLeft = options.positionLeft || false; // toast position - left or right
+ this.options.position = options.position || ''; // toast position - left or right
+ this.options.backgroundColor = options.backgroundColor; // toast background color
+ this.options.avatar = options.avatar || ""; // img element src - url or a path
+ this.options.className = options.className || ""; // additional class names for the toast
+ this.options.stopOnFocus = options.stopOnFocus === undefined? true: options.stopOnFocus; // stop timeout on focus
+ this.options.onClick = options.onClick; // Callback after click
+
+ // Returning the current object for chaining functions
+ return this;
+ },
+
+ // Building the DOM element
+ buildToast: function() {
+ // Validating if the options are defined
+ if (!this.options) {
+ throw "Toastify is not initialized";
+ }
+
+ // Creating the DOM object
+ var divElement = document.createElement("div");
+ divElement.className = "toastify on " + this.options.className;
+
+ // Positioning toast to left or right or center
+ if (!!this.options.position) {
+ divElement.className += " toastify-" + this.options.position;
+ } else {
+ // To be depreciated in further versions
+ if (this.options.positionLeft === true) {
+ divElement.className += " toastify-left";
+ console.warn('Property `positionLeft` will be depreciated in further versions. Please use `position` instead.')
+ } else {
+ // Default position
+ divElement.className += " toastify-right";
+ }
+ }
+
+ // Assigning gravity of element
+ divElement.className += " " + this.options.gravity;
+
+ if (this.options.backgroundColor) {
+ divElement.style.background = this.options.backgroundColor;
+ }
+
+ // Adding the toast message
+ divElement.innerHTML = this.options.text;
+
+ if (this.options.avatar !== "") {
+ var avatarElement = document.createElement("img");
+ avatarElement.src = this.options.avatar;
+
+ avatarElement.className = "toastify-avatar";
+
+ if (this.options.position == "left" || this.options.positionLeft === true) {
+ // Adding close icon on the left of content
+ divElement.appendChild(avatarElement);
+ } else {
+ // Adding close icon on the right of content
+ divElement.insertAdjacentElement("beforeend", avatarElement);
+ }
+ }
+
+ // Adding a close icon to the toast
+ if (this.options.close === true) {
+ // Create a span for close element
+ var closeElement = document.createElement("span");
+ closeElement.innerHTML = "✖";
+
+ closeElement.className = "toast-close";
+
+ // Triggering the removal of toast from DOM on close click
+ closeElement.addEventListener(
+ "click",
+ function(event) {
+ event.stopPropagation();
+ this.removeElement(this.toastElement);
+ window.clearTimeout(this.toastElement.timeOutValue);
+ }.bind(this)
+ );
+
+ //Calculating screen width
+ var width = window.innerWidth > 0 ? window.innerWidth : screen.width;
+
+ // Adding the close icon to the toast element
+ // Display on the right if screen width is less than or equal to 360px
+ if ((this.options.position == "left" || this.options.positionLeft === true) && width > 360) {
+ // Adding close icon on the left of content
+ divElement.insertAdjacentElement("afterbegin", closeElement);
+ } else {
+ // Adding close icon on the right of content
+ divElement.appendChild(closeElement);
+ }
+ }
+
+ // Clear timeout while toast is focused
+ if (this.options.stopOnFocus && this.options.duration > 0) {
+ const self = this;
+ // stop countdown
+ divElement.addEventListener(
+ "mouseover",
+ function(event) {
+ window.clearTimeout(divElement.timeOutValue);
+ }
+ )
+ // add back the timeout
+ divElement.addEventListener(
+ "mouseleave",
+ function() {
+ divElement.timeOutValue = window.setTimeout(
+ function() {
+ // Remove the toast from DOM
+ self.removeElement(divElement);
+ },
+ self.options.duration
+ )
+ }
+ )
+ }
+
+ // Adding an on-click destination path
+ if (typeof this.options.destination !== "undefined") {
+ divElement.addEventListener(
+ "click",
+ function(event) {
+ event.stopPropagation();
+ if (this.options.newWindow === true) {
+ window.open(this.options.destination, "_blank");
+ } else {
+ window.location = this.options.destination;
+ }
+ }.bind(this)
+ );
+ }
+
+ if (typeof this.options.onClick === "function" && typeof this.options.destination === "undefined") {
+ divElement.addEventListener(
+ "click",
+ function(event) {
+ event.stopPropagation();
+ this.options.onClick();
+ }.bind(this)
+ );
+ }
+
+ // Returning the generated element
+ return divElement;
+ },
+
+ // Displaying the toast
+ showToast: function() {
+ // Creating the DOM object for the toast
+ this.toastElement = this.buildToast();
+
+ // Getting the root element to with the toast needs to be added
+ var rootElement;
+ if (typeof this.options.selector === "undefined") {
+ rootElement = document.body;
+ } else {
+ rootElement = document.getElementById(this.options.selector);
+ }
+
+ // Validating if root element is present in DOM
+ if (!rootElement) {
+ throw "Root element is not defined";
+ }
+
+ // Adding the DOM element
+ rootElement.insertBefore(this.toastElement, rootElement.firstChild);
+
+ // Repositioning the toasts in case multiple toasts are present
+ Toastify.reposition();
+
+ if (this.options.duration > 0) {
+ this.toastElement.timeOutValue = window.setTimeout(
+ function() {
+ // Remove the toast from DOM
+ this.removeElement(this.toastElement);
+ }.bind(this),
+ this.options.duration
+ ); // Binding `this` for function invocation
+ }
+
+ // Supporting function chaining
+ return this;
+ },
+
+ hideToast: function() {
+ if (this.toastElement.timeOutValue) {
+ clearTimeout(this.toastElement.timeOutValue);
+ }
+ this.removeElement(this.toastElement);
+ },
+
+ // Removing the element from the DOM
+ removeElement: function(toastElement) {
+ // Hiding the element
+ // toastElement.classList.remove("on");
+ toastElement.className = toastElement.className.replace(" on", "");
+
+ // Removing the element from DOM after transition end
+ window.setTimeout(
+ function() {
+ // Remove the elemenf from the DOM, only when the parent node was not removed before.
+ if (toastElement.parentNode) {
+ toastElement.parentNode.removeChild(toastElement);
+ }
+
+ // Calling the callback function
+ this.options.callback.call(toastElement);
+
+ // Repositioning the toasts again
+ Toastify.reposition();
+ }.bind(this),
+ 400
+ ); // Binding `this` for function invocation
+ },
+ };
+
+ // Positioning the toasts on the DOM
+ Toastify.reposition = function() {
+ // Top margins with gravity
+ var topLeftOffsetSize = {
+ top: 15,
+ bottom: 15,
+ };
+ var topRightOffsetSize = {
+ top: 15,
+ bottom: 15,
+ };
+ var offsetSize = {
+ top: 15,
+ bottom: 15,
+ };
+
+ // Get all toast messages on the DOM
+ var allToasts = document.getElementsByClassName("toastify");
+
+ var classUsed;
+
+ // Modifying the position of each toast element
+ for (var i = 0; i < allToasts.length; i++) {
+ // Getting the applied gravity
+ if (containsClass(allToasts[i], "toastify-top") === true) {
+ classUsed = "toastify-top";
+ } else {
+ classUsed = "toastify-bottom";
+ }
+
+ var height = allToasts[i].offsetHeight;
+ classUsed = classUsed.substr(9, classUsed.length-1)
+ // Spacing between toasts
+ var offset = 15;
+
+ var width = window.innerWidth > 0 ? window.innerWidth : screen.width;
+
+ // Show toast in center if screen with less than or qual to 360px
+ if (width <= 360) {
+ // Setting the position
+ allToasts[i].style[classUsed] = offsetSize[classUsed] + "px";
+
+ offsetSize[classUsed] += height + offset;
+ } else {
+ if (containsClass(allToasts[i], "toastify-left") === true) {
+ // Setting the position
+ allToasts[i].style[classUsed] = topLeftOffsetSize[classUsed] + "px";
+
+ topLeftOffsetSize[classUsed] += height + offset;
+ } else {
+ // Setting the position
+ allToasts[i].style[classUsed] = topRightOffsetSize[classUsed] + "px";
+
+ topRightOffsetSize[classUsed] += height + offset;
+ }
+ }
+ }
+
+ // Supporting function chaining
+ return this;
+ };
+
+ function containsClass(elem, yourClass) {
+ if (!elem || typeof yourClass !== "string") {
+ return false;
+ } else if (
+ elem.className &&
+ elem.className
+ .trim()
+ .split(/\s+/gi)
+ .indexOf(yourClass) > -1
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // Setting up the prototype for the init object
+ Toastify.lib.init.prototype = Toastify.lib;
+
+ // Returning the Toastify function to be assigned to the window object/module
+ return Toastify;
+});