From 8dbd1b892cd6d4d513c780185f26e071cae2f91c Mon Sep 17 00:00:00 2001 From: RemixDev Date: Sat, 11 Apr 2020 12:51:22 +0200 Subject: [PATCH] Added toast messages --- public/css/progressbar.css | 27 +++ public/css/style.css | 7 + public/css/toastify.css | 78 +++++++++ public/index.html | 10 +- public/js/downloadList.js | 6 + public/js/init.js | 48 +++++ public/js/toastify.js | 351 +++++++++++++++++++++++++++++++++++++ 7 files changed, 524 insertions(+), 3 deletions(-) create mode 100644 public/css/toastify.css create mode 100644 public/js/toastify.js 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; +});