From 2b88fcf1f08ca3ea65311c4521fa248fbc06b636 Mon Sep 17 00:00:00 2001 From: andOrlando <59105749+andOrlando@users.noreply.github.com> Date: Sat, 19 Dec 2020 16:45:14 -0500 Subject: [PATCH 01/17] adds snackbar support adds the snackbar class --- public/css/home.css | 66 +++++++++++++++ public/js/home.js | 192 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 258 insertions(+) diff --git a/public/css/home.css b/public/css/home.css index 8c8bc344..a14a9640 100644 --- a/public/css/home.css +++ b/public/css/home.css @@ -52,6 +52,7 @@ body { --black: #000000; --transparent-black: rgba(0, 0, 0, 0.6); + --transparent-black-lite: rgba(0, 0, 0, 0.2); --blue: #2196F3; --blue1: #9ebcea; @@ -1340,3 +1341,68 @@ hr { #more-versions a { font-size: 120%; } + +.snackbar { + height: 44px; + width: auto; + background-color: var(--gray); + position: fixed; + left: 17px; + bottom: 10px; + + transform: translateY(0px); + transition: transform 250ms; +} + +.snackbar.hidden { + transform: translateY(54px); + transition: transform 250ms; +} + +.snackbar button { + height: 32px; + margin-top: 6px; + margin-bottom: 6px; + margin-right: 4px; + position: relative; + float: right; + border: none; + background-color: var(--gray); + cursor: pointer; +} + +.snackbar button::after { + content: ''; + height: 100%; + width: 100%; + position: absolute; + top: 0; + left: 0; + background-color:var(--transparent-black-lite); + z-index: 1; + opacity: 0; + + transition: opacity 100ms ease-in; +} + +.snackbar button:hover::after { + opacity: 1; +} + +.snackbar button:focus { + outline: none; +} + +.snackbar span { + display: inline-block; + vertical-align: middle; + line-height: 44px; + padding: 0px 8px; +} + +.snackbar button span { + line-height: 32px; + font-weight: normal; + font-size: 16px; + font-family: 'Poppins-Regular'; +} diff --git a/public/js/home.js b/public/js/home.js index 894787fb..246cfdb4 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -1194,6 +1194,198 @@ function closeSideNav() { // Allows exiting sidenav by clicking anywhere outside document.getElementById("sidenav-overlay").addEventListener("click", closeSideNav); + +class Snackbar { + + static snackbars = {} + static snackbarIDs = [] + + static getSnackbarByID(id) { + return snackbarIDs[id] + } + + constructor(text, options) { + this.text = text; + this.color = options["color"]; + this.textColor = options["textColor"]; + this.buttonText = options["buttonText"]; + this.buttonOnClick = options["buttonOnClick"]; + this.destroyWhenButtonClicked = options["destroyWhenButtonClicked"] || true; + this.bodyOnClick = options["bodyOnClick"]; + + this.id; + } + + /** + * creates the snackbar in the HTML + * returns the snackbar object + */ + make() { + + //stops if the element already exists + if (typeof document.getElementById(`sidenav-${this.id}`) === undefined) { + return; + } + + if (this.id === undefined) { + this.id = this.createID(); + } + + //creates the snackbar and gives it classes and text + const snackbarNode = document.createElement("DIV"); + snackbarNode.classList.add("snackbar") + snackbarNode.classList.add("hidden") + + //assigns it id based off of it's actual id + snackbarNode.id = `snackbar-${this.id}` + + //adds color if given + if (this.color !== undefined) { + snackbarNode.style.backgroundColor = `${this.color}` + } + + //sets the onClick with which just destroys it by default + snackbarNode.setAttribute("onclick", this.onBodyClick || `Snackbar.snackbars[${this.id}].destroy();`); + + //adds the text + const textNode = document.createElement("SPAN") + textNode.textContent = this.text + + //colors the text if necessary + if (this.textColor === undefined) { + textNode.style.color = this.textColor + } + + //adds the text + snackbarNode.appendChild(textNode) + + //makes the button if given button parameters + if (this.buttonText !== undefined && this.buttonOnClick != undefined) { + + //creates the button and adds class + const buttonNode = document.createElement("BUTTON") + + //creates the text span + const buttonTextNode = document.createElement("SPAN") + buttonTextNode.textContent = this.buttonText + + //colors the text if necessary + if (this.textColor !== undefined) { + buttonTextNode.style.color = this.textColor + } + + if (this.color !== undefined) { + buttonNode.style.backgroundColor = this.color + } + + //adds the node + buttonNode.appendChild(buttonTextNode) + + //adds the onclick and if destroyWhenButtonClicked is true, destroys when the button is clicked (otherwise does nothing) + buttonNode.setAttribute("onclick", `${this.buttonOnClick};${this.destroyWhenButtonClicked ? ` Snackbar.snackbars[${this.id}].destroy();` : ""}`) + + snackbarNode.appendChild(buttonNode) + } + + //adds the node to the body, puts reference to DOM element in this.element + document.body.appendChild(snackbarNode); + this.element = document.getElementById(`snackbar-${this.id}`) + + return this; + } + + /** + * shows the snackbar + * if it's not already made, makes it + * if you need to show right after making, use this + * returns the snackbar object + */ + show() { + const snackbar = this + + //function for the callback for setTimeout + const removeHidden = function() { + snackbar.element.classList.remove("hidden"); + } + + //if not already made, makes the snackbar + if (this.element === undefined) { + this.make(); + setTimeout(removeHidden, 250); + return this; + + } else { + removeHidden(); + return this; + } + } + + /** + * hides the snackbar + * returns the snackbar object + */ + hide() { + this.element.classList.add("hidden"); + return this; + } + + /** + * destroys the snackbar, its references and its ID + * if it's not already hidden, hides it unless override is true + */ + destroy() { + + const snackbar = this + + //function which deletes the references and ids + const finalizeDeletion = function() { + snackbar.element.remove(); + delete Snackbar.snackbarIDs[Snackbar.snackbarIDs.indexOf(snackbar.id)]; + delete Snackbar.snackbars[snackbar.id]; + snackbar.id = undefined; + } + + //if it's not hidden it shouldn't just dissapear + if (this.element.classList.contains("hidden")) { + finalizeDeletion() + } else { + this.hide() + //deletes it as soon as it's actually hidden + setTimeout(finalizeDeletion, 250) + } + } + + /** + * creates and reserves the ID for this snackbar + * also creates its reference in snackbars + */ + createID() { + + let id = null; + + //goes through all consecutive numbers to find an id + let iterator = 0 + while (id === null) { + //checks if the id already exists, otherwise continues to iterate + Snackbar.snackbarIDs.includes(iterator) ? iterator++ : id = iterator; + } + + Snackbar.snackbarIDs.push(id); + Snackbar.snackbars[id] = this + + return id; + } + +} + +/** + * destroys a snackbar taking an ID + */ +function destroySnackbar(id) { + Snackbar.snackbars[id].destroy() +} + + $("#export_button").click(() => { prefs = {}; From b8a9952441a86e2c7ceabf9e52a32d4e78ad8365 Mon Sep 17 00:00:00 2001 From: andOrlando <59105749+andOrlando@users.noreply.github.com> Date: Sat, 19 Dec 2020 22:24:27 -0500 Subject: [PATCH 02/17] adds undo snackbar and fixes bugs - adds colors for undo snackbar (to be edited) - improves resize logic for snackbar at low screensizes - When assignment is deleted, replaces item with a placeholder that's ignored by tabulator which gets deleted if the snackbar times out - adds a bunch of semicolons - better document snackbar options - adds timeout and funciton on timeout - improves instant .show() logic - improves oncick things - fixes lots of other bugs --- public/css/home.css | 21 ++++-- public/js/buttonFunctions.js | 19 +++++- public/js/home.js | 120 +++++++++++++++++++++++------------ 3 files changed, 112 insertions(+), 48 deletions(-) diff --git a/public/css/home.css b/public/css/home.css index a14a9640..daaf9b9e 100644 --- a/public/css/home.css +++ b/public/css/home.css @@ -64,6 +64,7 @@ body { --orange: #f6b93b; --red: #ff0000; + --red1: #800000; --schedule1: #63c082; --schedule2: #72c68e; @@ -106,6 +107,7 @@ body.dark { --orange: #f6b93b; --red: #ff0000; + --red1: #ff0000; --schedule1: #00290e; --schedule2: #003d14; @@ -1343,12 +1345,16 @@ hr { } .snackbar { + display: flex; height: 44px; width: auto; + max-width: calc(100% - 30px); background-color: var(--gray); position: fixed; - left: 17px; + left: 0; bottom: 10px; + margin: 0px 15px; + box-shadow: 2px 2px 5px var(--transparent-black); transform: translateY(0px); transition: transform 250ms; @@ -1361,14 +1367,13 @@ hr { .snackbar button { height: 32px; - margin-top: 6px; - margin-bottom: 6px; - margin-right: 4px; + margin: 6px 9px 6px 3px; position: relative; - float: right; border: none; background-color: var(--gray); cursor: pointer; + white-space: nowrap; + order: 2; } .snackbar button::after { @@ -1394,10 +1399,14 @@ hr { } .snackbar span { - display: inline-block; + width: auto; vertical-align: middle; line-height: 44px; padding: 0px 8px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + order: 1; } .snackbar button span { diff --git a/public/js/buttonFunctions.js b/public/js/buttonFunctions.js index ee546259..d3e11a0a 100755 --- a/public/js/buttonFunctions.js +++ b/public/js/buttonFunctions.js @@ -19,6 +19,21 @@ let newAssignment = function() { } } +function replaceAssignmentFromID(oldData, newData, classID) { + const assignments = currentTableData.currentTermData.classes[classID].assignments + + //this weird version of indexOf is necessary (I think) because when comparing the two it doesn't always match correctly + let index = -1; + for (i=0; i { + return obj.placeholder !== true + })); currentTableData.currentTermData.calcGPA = computeGPA(currentTableData.currentTermData.classes); currentTableData.terms[currentTerm].calcGPA = computeGPA(currentTableData.currentTermData.classes); diff --git a/public/js/home.js b/public/js/home.js index 246cfdb4..59a67ac2 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -581,7 +581,18 @@ let assignmentsTable = new Tabulator("#assignmentsTable", { width: 40, align: "center", cellClick: function(e, cell) { - cell.getRow().delete(); + + const data = cell.getRow().getData() + replaceAssignmentFromID(data, {assignment_id: data["assignment_id"], placeholder: true}, selected_class_i); + + new Snackbar(`You deleted ${data["name"]}`, { + color: "var(--red1)", + textColor: "var(--white)", + buttonText: "Undo?", + buttonClick: () => {replaceAssignmentFromID({assignment_id: data["assignment_id"], placeholder: true}, data, selected_class_i)}, + // timeout: 7500, + timeoutFunction: () => {cell.getRow().delete()} + }).show(); }, headerSort: false, cssClass: "icon-col allow-overflow" @@ -1197,27 +1208,50 @@ document.getElementById("sidenav-overlay").addEventListener("click", closeSideNa class Snackbar { - static snackbars = {} - static snackbarIDs = [] + static snackbars = {}; + static snackbarIDs = []; static getSnackbarByID(id) { - return snackbarIDs[id] + return snackbarIDs[id]; } + /** + * text is the main requirement, and it's just text + * color: String - string reference to a color or a variable, sets the background color + * textColor: String - string reference to a color or a variable, sets the text color + * buttonText: String - Sets the button text, both it and buttonClick have to be defined for the button to show + * buttonClick: Function - Sets the button's onclick logic, both it and buttonText have to be defined for the button to show + * destroyWhenButtonClicked : Boolean - Whether or not it should destroy itself when the button is clicked, defaults to true + * bodyClick: Function - Sets the body's onclick logic + * destroyWhenBodyClicked : Boolean - Whether or not it should destroy itself when the body is clicked, defaults to true + * timeout: Int - Time in ms + * timeoutFunction: Function - What to run on timeout + */ constructor(text, options) { this.text = text; this.color = options["color"]; this.textColor = options["textColor"]; this.buttonText = options["buttonText"]; - this.buttonOnClick = options["buttonOnClick"]; + this.buttonClick = options["buttonClick"]; this.destroyWhenButtonClicked = options["destroyWhenButtonClicked"] || true; - this.bodyOnClick = options["bodyOnClick"]; + this.bodyClick = options["bodyClick"]; + this.destroyWhenBodyClicked = options["destroyWhenBodyClicked"] || true; + + //timeout logic + const timeoutFunction = options["timeoutFunction"] === undefined ? options["timeoutFunction"] : () => {}; + if (options["timeout"] !== undefined) { + setTimeout(() => { + timeoutFunction(); + this.destroy(); + }, options["timeout"]) + } this.id; } /** * creates the snackbar in the HTML + * if you want to show it as soon as you make it, use show without calling make instead * returns the snackbar object */ make() { @@ -1233,63 +1267,73 @@ class Snackbar { //creates the snackbar and gives it classes and text const snackbarNode = document.createElement("DIV"); - snackbarNode.classList.add("snackbar") - snackbarNode.classList.add("hidden") + snackbarNode.classList.add("snackbar"); + snackbarNode.classList.add("hidden"); //assigns it id based off of it's actual id - snackbarNode.id = `snackbar-${this.id}` + snackbarNode.id = `snackbar-${this.id}`; //adds color if given if (this.color !== undefined) { - snackbarNode.style.backgroundColor = `${this.color}` + snackbarNode.style.backgroundColor = `${this.color}`; } //sets the onClick with which just destroys it by default - snackbarNode.setAttribute("onclick", this.onBodyClick || `Snackbar.snackbars[${this.id}].destroy();`); + const bodyOnClickFunction = this.bodyClick !== undefined ? () => {this.bodyClick();} : () => {}; + const destroyFromBody = this.destroyWhenBodyClicked ? () => {this.destroy();} : () => {}; + snackbarNode.addEventListener("click", () => { + bodyOnClickFunction(); + destroyFromBody(); + }) //adds the text - const textNode = document.createElement("SPAN") - textNode.textContent = this.text + const textNode = document.createElement("SPAN"); + textNode.textContent = this.text; //colors the text if necessary - if (this.textColor === undefined) { - textNode.style.color = this.textColor + if (this.textColor !== undefined) { + textNode.style.color = this.textColor; } //adds the text - snackbarNode.appendChild(textNode) + snackbarNode.appendChild(textNode); //makes the button if given button parameters - if (this.buttonText !== undefined && this.buttonOnClick != undefined) { + if (this.buttonText !== undefined && this.buttonClick != undefined) { //creates the button and adds class - const buttonNode = document.createElement("BUTTON") + const buttonNode = document.createElement("BUTTON"); //creates the text span - const buttonTextNode = document.createElement("SPAN") - buttonTextNode.textContent = this.buttonText + const buttonTextNode = document.createElement("SPAN"); + buttonTextNode.textContent = this.buttonText; //colors the text if necessary if (this.textColor !== undefined) { - buttonTextNode.style.color = this.textColor + buttonTextNode.style.color = this.textColor; } if (this.color !== undefined) { - buttonNode.style.backgroundColor = this.color + buttonNode.style.backgroundColor = this.color; } //adds the node - buttonNode.appendChild(buttonTextNode) + buttonNode.appendChild(buttonTextNode); //adds the onclick and if destroyWhenButtonClicked is true, destroys when the button is clicked (otherwise does nothing) - buttonNode.setAttribute("onclick", `${this.buttonOnClick};${this.destroyWhenButtonClicked ? ` Snackbar.snackbars[${this.id}].destroy();` : ""}`) - - snackbarNode.appendChild(buttonNode) + const destroyFromButton = this.destroyWhenButtonClicked ? () => {this.destroy();} : () => {}; + buttonNode.addEventListener("click", (event) => { + this.buttonClick(); + destroyFromButton(); + event.stopPropagation(); + }) + + snackbarNode.appendChild(buttonNode); } //adds the node to the body, puts reference to DOM element in this.element document.body.appendChild(snackbarNode); - this.element = document.getElementById(`snackbar-${this.id}`) + this.element = document.getElementById(`snackbar-${this.id}`); return this; } @@ -1301,7 +1345,7 @@ class Snackbar { * returns the snackbar object */ show() { - const snackbar = this + const snackbar = this; //function for the callback for setTimeout const removeHidden = function() { @@ -1309,7 +1353,7 @@ class Snackbar { } //if not already made, makes the snackbar - if (this.element === undefined) { + if (document.getElementById(`snackbar-${this.id}`) === null) { this.make(); setTimeout(removeHidden, 250); return this; @@ -1335,7 +1379,7 @@ class Snackbar { */ destroy() { - const snackbar = this + const snackbar = this; //function which deletes the references and ids const finalizeDeletion = function() { @@ -1345,13 +1389,15 @@ class Snackbar { snackbar.id = undefined; } + console.log(this.element.classList.contains("hidden")) + //if it's not hidden it shouldn't just dissapear if (this.element.classList.contains("hidden")) { - finalizeDeletion() + finalizeDeletion(); } else { - this.hide() + this.hide(); //deletes it as soon as it's actually hidden - setTimeout(finalizeDeletion, 250) + setTimeout(finalizeDeletion, 250); } } @@ -1378,14 +1424,6 @@ class Snackbar { } -/** - * destroys a snackbar taking an ID - */ -function destroySnackbar(id) { - Snackbar.snackbars[id].destroy() -} - - $("#export_button").click(() => { prefs = {}; From 56b2d5716053983a33ae6e7b9a95b17cbc60eb34 Mon Sep 17 00:00:00 2001 From: andOrlando <59105749+andOrlando@users.noreply.github.com> Date: Sat, 19 Dec 2020 22:27:43 -0500 Subject: [PATCH 03/17] improves colors --- public/css/home.css | 4 ++-- public/js/home.js | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/public/css/home.css b/public/css/home.css index daaf9b9e..18a3de08 100644 --- a/public/css/home.css +++ b/public/css/home.css @@ -64,7 +64,7 @@ body { --orange: #f6b93b; --red: #ff0000; - --red1: #800000; + --red1: #a70000; --schedule1: #63c082; --schedule2: #72c68e; @@ -107,7 +107,7 @@ body.dark { --orange: #f6b93b; --red: #ff0000; - --red1: #ff0000; + --red1: #ff8585; --schedule1: #00290e; --schedule2: #003d14; diff --git a/public/js/home.js b/public/js/home.js index 59a67ac2..69be036d 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -590,7 +590,7 @@ let assignmentsTable = new Tabulator("#assignmentsTable", { textColor: "var(--white)", buttonText: "Undo?", buttonClick: () => {replaceAssignmentFromID({assignment_id: data["assignment_id"], placeholder: true}, data, selected_class_i)}, - // timeout: 7500, + timeout: 7500, timeoutFunction: () => {cell.getRow().delete()} }).show(); }, @@ -1389,8 +1389,6 @@ class Snackbar { snackbar.id = undefined; } - console.log(this.element.classList.contains("hidden")) - //if it's not hidden it shouldn't just dissapear if (this.element.classList.contains("hidden")) { finalizeDeletion(); From 79f6c8a79d27c4910c849feaca0a7caf13a18b26 Mon Sep 17 00:00:00 2001 From: andOrlando <59105749+andOrlando@users.noreply.github.com> Date: Sat, 19 Dec 2020 22:54:01 -0500 Subject: [PATCH 04/17] fixes placeholder deletion - makes placeholder deletion actually delete from the list - also adds some semicolons --- public/js/buttonFunctions.js | 17 +++++++++++++++-- public/js/home.js | 5 +++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/public/js/buttonFunctions.js b/public/js/buttonFunctions.js index d3e11a0a..3167a7ea 100755 --- a/public/js/buttonFunctions.js +++ b/public/js/buttonFunctions.js @@ -19,8 +19,13 @@ let newAssignment = function() { } } +function dothething() { + const assignments = currentTableData.currentTermData.classes[selected_class_i].assignments; + console.log(assignments) +} + function replaceAssignmentFromID(oldData, newData, classID) { - const assignments = currentTableData.currentTermData.classes[classID].assignments + const assignments = currentTableData.currentTermData.classes[classID].assignments; //this weird version of indexOf is necessary (I think) because when comparing the two it doesn't always match correctly let index = -1; @@ -30,10 +35,18 @@ function replaceAssignmentFromID(oldData, newData, classID) { break; } } - assignments[index] = newData + assignments[index] = newData; updateGradePage(); } +function removeAssignmentFromID(id, classID) { + const assignments = currentTableData.currentTermData.classes[classID].assignments; + const newArray = assignments.filter((obj) => { + return obj["assignment_id"] !== id; + }); + assignments.length = 0 + assignments.push.apply(assignments, newArray) +} let editAssignment = function(data) { diff --git a/public/js/home.js b/public/js/home.js index 69be036d..f1daef3d 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -589,9 +589,10 @@ let assignmentsTable = new Tabulator("#assignmentsTable", { color: "var(--red1)", textColor: "var(--white)", buttonText: "Undo?", - buttonClick: () => {replaceAssignmentFromID({assignment_id: data["assignment_id"], placeholder: true}, data, selected_class_i)}, + buttonClick: () => {replaceAssignmentFromID({assignment_id: data["assignment_id"], placeholder: true}, data, selected_class_i);}, timeout: 7500, - timeoutFunction: () => {cell.getRow().delete()} + timeoutFunction: () => {removeAssignmentFromID(data["assignment_id"], selected_class_i);}, + bodyClick: () => {removeAssignmentFromID(data["assignment_id"], selected_class_i);} }).show(); }, headerSort: false, From 8c3051f39c79866c9ff4ce427c92614cadc18d48 Mon Sep 17 00:00:00 2001 From: andOrlando <59105749+andOrlando@users.noreply.github.com> Date: Sun, 20 Dec 2020 12:39:16 -0500 Subject: [PATCH 05/17] code improvements adds some semicolons, deletes test function --- public/js/buttonFunctions.js | 9 ++------- public/js/home.js | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/public/js/buttonFunctions.js b/public/js/buttonFunctions.js index 3167a7ea..5283b7b5 100755 --- a/public/js/buttonFunctions.js +++ b/public/js/buttonFunctions.js @@ -19,11 +19,6 @@ let newAssignment = function() { } } -function dothething() { - const assignments = currentTableData.currentTermData.classes[selected_class_i].assignments; - console.log(assignments) -} - function replaceAssignmentFromID(oldData, newData, classID) { const assignments = currentTableData.currentTermData.classes[classID].assignments; @@ -44,8 +39,8 @@ function removeAssignmentFromID(id, classID) { const newArray = assignments.filter((obj) => { return obj["assignment_id"] !== id; }); - assignments.length = 0 - assignments.push.apply(assignments, newArray) + assignments.length = 0; + assignments.push.apply(assignments, newArray); } let editAssignment = function(data) { diff --git a/public/js/home.js b/public/js/home.js index f1daef3d..006b21d5 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -1212,10 +1212,6 @@ class Snackbar { static snackbars = {}; static snackbarIDs = []; - static getSnackbarByID(id) { - return snackbarIDs[id]; - } - /** * text is the main requirement, and it's just text * color: String - string reference to a color or a variable, sets the background color @@ -1244,7 +1240,7 @@ class Snackbar { setTimeout(() => { timeoutFunction(); this.destroy(); - }, options["timeout"]) + }, options["timeout"]); } this.id; @@ -1262,11 +1258,12 @@ class Snackbar { return; } + //gives it an ID if it doesn't already have one if (this.id === undefined) { - this.id = this.createID(); + this.createID(); } - //creates the snackbar and gives it classes and text + //creates the snackbar and gives it classes const snackbarNode = document.createElement("DIV"); snackbarNode.classList.add("snackbar"); snackbarNode.classList.add("hidden"); @@ -1279,7 +1276,7 @@ class Snackbar { snackbarNode.style.backgroundColor = `${this.color}`; } - //sets the onClick with which just destroys it by default + //sets the body onclick listener which just destroys it by default const bodyOnClickFunction = this.bodyClick !== undefined ? () => {this.bodyClick();} : () => {}; const destroyFromBody = this.destroyWhenBodyClicked ? () => {this.destroy();} : () => {}; snackbarNode.addEventListener("click", () => { @@ -1296,7 +1293,7 @@ class Snackbar { textNode.style.color = this.textColor; } - //adds the text + //adds the text node snackbarNode.appendChild(textNode); //makes the button if given button parameters @@ -1318,14 +1315,15 @@ class Snackbar { buttonNode.style.backgroundColor = this.color; } - //adds the node + //adds the text node buttonNode.appendChild(buttonTextNode); - //adds the onclick and if destroyWhenButtonClicked is true, destroys when the button is clicked (otherwise does nothing) + //sets the button onclick listener which runs the given funtion and destroys the snackar by default const destroyFromButton = this.destroyWhenButtonClicked ? () => {this.destroy();} : () => {}; buttonNode.addEventListener("click", (event) => { this.buttonClick(); destroyFromButton(); + //stops propogation so the body event isn't called event.stopPropagation(); }) @@ -1356,7 +1354,8 @@ class Snackbar { //if not already made, makes the snackbar if (document.getElementById(`snackbar-${this.id}`) === null) { this.make(); - setTimeout(removeHidden, 250); + //waits a momement to make sure the snackbar's animation functions properly + setTimeout(removeHidden, 10); return this; } else { @@ -1416,7 +1415,8 @@ class Snackbar { } Snackbar.snackbarIDs.push(id); - Snackbar.snackbars[id] = this + Snackbar.snackbars[id] = this; + this.id = id; return id; } From 97e5581684bf9f0c4473f20b155a38d9eaff913b Mon Sep 17 00:00:00 2001 From: andOrlando <59105749+andOrlando@users.noreply.github.com> Date: Mon, 21 Dec 2020 12:02:55 -0500 Subject: [PATCH 06/17] Apply suggestions from code review Co-authored-by: psvenk <45520974+psvenk@users.noreply.github.com> --- public/js/buttonFunctions.js | 26 +++++++++++--------------- public/js/home.js | 10 +++------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/public/js/buttonFunctions.js b/public/js/buttonFunctions.js index 5283b7b5..c71fd458 100755 --- a/public/js/buttonFunctions.js +++ b/public/js/buttonFunctions.js @@ -22,25 +22,20 @@ let newAssignment = function() { function replaceAssignmentFromID(oldData, newData, classID) { const assignments = currentTableData.currentTermData.classes[classID].assignments; - //this weird version of indexOf is necessary (I think) because when comparing the two it doesn't always match correctly - let index = -1; - for (i=0; i + assignment_id === oldData.assignment_id + ); assignments[index] = newData; updateGradePage(); } function removeAssignmentFromID(id, classID) { const assignments = currentTableData.currentTermData.classes[classID].assignments; - const newArray = assignments.filter((obj) => { - return obj["assignment_id"] !== id; - }); + const newArray = assignments.filter(({ assignment_id }) => + assignment_id !== id + ); assignments.length = 0; - assignments.push.apply(assignments, newArray); + assignments.push(...newArray); } let editAssignment = function(data) { @@ -151,9 +146,10 @@ let updateGradePage = function() { classesTable.replaceData(currentTableData.currentTermData.classes); categoriesTable.setData(currentTableData.currentTermData.classes[selected_class_i].categoryDisplay); - assignmentsTable.replaceData(currentTableData.currentTermData.classes[selected_class_i].assignments.filter((obj) => { - return obj.placeholder !== true - })); + assignmentsTable.replaceData(currentTableData.currentTermData + .classes[selected_class_i].assignments + .filter(({ placeholder }) => !placeholder) + ); currentTableData.currentTermData.calcGPA = computeGPA(currentTableData.currentTermData.classes); currentTableData.terms[currentTerm].calcGPA = computeGPA(currentTableData.currentTermData.classes); diff --git a/public/js/home.js b/public/js/home.js index 006b21d5..401d5258 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -581,7 +581,6 @@ let assignmentsTable = new Tabulator("#assignmentsTable", { width: 40, align: "center", cellClick: function(e, cell) { - const data = cell.getRow().getData() replaceAssignmentFromID(data, {assignment_id: data["assignment_id"], placeholder: true}, selected_class_i); @@ -589,10 +588,10 @@ let assignmentsTable = new Tabulator("#assignmentsTable", { color: "var(--red1)", textColor: "var(--white)", buttonText: "Undo?", - buttonClick: () => {replaceAssignmentFromID({assignment_id: data["assignment_id"], placeholder: true}, data, selected_class_i);}, + buttonClick: () => replaceAssignmentFromID({ assignment_id: data["assignment_id"], placeholder: true }, data, selected_class_i), timeout: 7500, - timeoutFunction: () => {removeAssignmentFromID(data["assignment_id"], selected_class_i);}, - bodyClick: () => {removeAssignmentFromID(data["assignment_id"], selected_class_i);} + timeoutFunction: () => removeAssignmentFromID(data["assignment_id"], selected_class_i), + bodyClick: () => removeAssignmentFromID(data["assignment_id"], selected_class_i), }).show(); }, headerSort: false, @@ -1252,7 +1251,6 @@ class Snackbar { * returns the snackbar object */ make() { - //stops if the element already exists if (typeof document.getElementById(`sidenav-${this.id}`) === undefined) { return; @@ -1378,7 +1376,6 @@ class Snackbar { * if it's not already hidden, hides it unless override is true */ destroy() { - const snackbar = this; //function which deletes the references and ids @@ -1404,7 +1401,6 @@ class Snackbar { * also creates its reference in snackbars */ createID() { - let id = null; //goes through all consecutive numbers to find an id From b83ed68d51e5306ccf20deb607dbf4a8973c7f0e Mon Sep 17 00:00:00 2001 From: andOrlando <59105749+andOrlando@users.noreply.github.com> Date: Mon, 21 Dec 2020 13:06:38 -0500 Subject: [PATCH 07/17] Apply suggestions from code review Co-authored-by: psvenk <45520974+psvenk@users.noreply.github.com> --- public/js/home.js | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/public/js/home.js b/public/js/home.js index 401d5258..9fcb9c7a 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -1275,8 +1275,8 @@ class Snackbar { } //sets the body onclick listener which just destroys it by default - const bodyOnClickFunction = this.bodyClick !== undefined ? () => {this.bodyClick();} : () => {}; - const destroyFromBody = this.destroyWhenBodyClicked ? () => {this.destroy();} : () => {}; + const bodyOnClickFunction = this.bodyClick !== undefined ? () => this.bodyClick() : () => {}; + const destroyFromBody = this.destroyWhenBodyClicked ? () => this.destroy() : () => {}; snackbarNode.addEventListener("click", () => { bodyOnClickFunction(); destroyFromBody(); @@ -1296,7 +1296,6 @@ class Snackbar { //makes the button if given button parameters if (this.buttonText !== undefined && this.buttonClick != undefined) { - //creates the button and adds class const buttonNode = document.createElement("BUTTON"); @@ -1317,8 +1316,8 @@ class Snackbar { buttonNode.appendChild(buttonTextNode); //sets the button onclick listener which runs the given funtion and destroys the snackar by default - const destroyFromButton = this.destroyWhenButtonClicked ? () => {this.destroy();} : () => {}; - buttonNode.addEventListener("click", (event) => { + const destroyFromButton = this.destroyWhenButtonClicked ? () => this.destroy() : () => {}; + buttonNode.addEventListener("click", event => { this.buttonClick(); destroyFromButton(); //stops propogation so the body event isn't called @@ -1342,12 +1341,7 @@ class Snackbar { * returns the snackbar object */ show() { - const snackbar = this; - - //function for the callback for setTimeout - const removeHidden = function() { - snackbar.element.classList.remove("hidden"); - } + const removeHidden = () => this.element.classList.remove("hidden"); //if not already made, makes the snackbar if (document.getElementById(`snackbar-${this.id}`) === null) { @@ -1355,7 +1349,6 @@ class Snackbar { //waits a momement to make sure the snackbar's animation functions properly setTimeout(removeHidden, 10); return this; - } else { removeHidden(); return this; @@ -1416,7 +1409,6 @@ class Snackbar { return id; } - } $("#export_button").click(() => { From e8bc87ddbb8a0af4371f3f16ee868af0b6438d2b Mon Sep 17 00:00:00 2001 From: andOrlando <59105749+andOrlando@users.noreply.github.com> Date: Wed, 23 Dec 2020 23:22:43 -0500 Subject: [PATCH 08/17] fixes and improves the timer - fixes bug where the timeout wouldn't run the correct function on timeout - fixes bug where the timeout would start when the snackbar is made - adds timeout modes for what to do when the timer finishes --- public/js/home.js | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/public/js/home.js b/public/js/home.js index 9fcb9c7a..a44511a6 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -1221,7 +1221,8 @@ class Snackbar { * bodyClick: Function - Sets the body's onclick logic * destroyWhenBodyClicked : Boolean - Whether or not it should destroy itself when the body is clicked, defaults to true * timeout: Int - Time in ms - * timeoutFunction: Function - What to run on timeout + * timeoutFunction: Function - What to run on timeout (doesn't run if hidden or destroyed) + * timeoutMode: can be "destroy", "hide", "none" or empty. Determines what to do on timeout, destroys by default */ constructor(text, options) { this.text = text; @@ -1234,14 +1235,26 @@ class Snackbar { this.destroyWhenBodyClicked = options["destroyWhenBodyClicked"] || true; //timeout logic - const timeoutFunction = options["timeoutFunction"] === undefined ? options["timeoutFunction"] : () => {}; - if (options["timeout"] !== undefined) { - setTimeout(() => { - timeoutFunction(); - this.destroy(); - }, options["timeout"]); + this.timeoutFunction = options["timeoutFunction"] !== undefined ? options["timeoutFunction"] : () => {}; + this.timeout = options["timeout"]; + this.timeoutInProgress; + + //what to run on timeout + this.timeoutEndFunction; + switch(options["timeoutMode"]) { + case "destroy": + case undefined: + this.timeoutEndFunction = () => this.destroy(); + break; + case "hide": + this.timeoutEndFunction = () => this.hide(); + break; + case "none": + this.timeoutEndFunction = () => {}; + break; } + //creates this.id this.id; } @@ -1341,6 +1354,15 @@ class Snackbar { * returns the snackbar object */ show() { + //starts the timeout + if (this.timeout !== undefined) { + this.timeoutInProgress = setTimeout(() => { + this.timeoutFunction(); + this.timeoutEndFunction(); + this.timeoutInProgress = undefined; //resets the timeoutInProgress variable at the end of the timeout + }, this.timeout); + } + const removeHidden = () => this.element.classList.remove("hidden"); //if not already made, makes the snackbar @@ -1360,6 +1382,11 @@ class Snackbar { * returns the snackbar object */ hide() { + if (this.timeoutInProgress !== undefined) { + clearTimeout(this.timeoutInProgress); + this.timeoutInProgress = undefined; + } + this.element.classList.add("hidden"); return this; } From 59f4bea0cd0956d17405d9bd0d651bf63aba91eb Mon Sep 17 00:00:00 2001 From: andOrlando <59105749+andOrlando@users.noreply.github.com> Date: Thu, 24 Dec 2020 12:42:39 -0500 Subject: [PATCH 09/17] fixes bug with new assignments - gives new assignments sequential IDs - filters the assignments when determing whether to show the info icon or not - changes right padding for button --- public/css/home.css | 2 +- public/js/buttonFunctions.js | 2 ++ public/js/home.js | 11 ++++++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/public/css/home.css b/public/css/home.css index 18a3de08..7d8bf86d 100644 --- a/public/css/home.css +++ b/public/css/home.css @@ -1367,7 +1367,7 @@ hr { .snackbar button { height: 32px; - margin: 6px 9px 6px 3px; + margin: 6px 6px 6px 3px; position: relative; border: none; background-color: var(--gray); diff --git a/public/js/buttonFunctions.js b/public/js/buttonFunctions.js index c71fd458..9ee86a3e 100755 --- a/public/js/buttonFunctions.js +++ b/public/js/buttonFunctions.js @@ -5,6 +5,7 @@ let newAssignment = function() { if (!isNaN(selected_class_i)) { currentTableData.currentTermData.classes[selected_class_i].assignments.unshift({ + "assignment_id": newAssignmentIDCounter.toString(), "name": "Assignment", "category": Object.keys(currentTableData.currentTermData.classes[selected_class_i].categories)[currentFilterRow >= 0 ? currentFilterRow : 0], "score": 10, @@ -13,6 +14,7 @@ let newAssignment = function() { "color": "green", "synthetic": "true", }); + newAssignmentIDCounter++; updateGradePage(); diff --git a/public/js/home.js b/public/js/home.js index a44511a6..d0a93287 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -36,6 +36,9 @@ let currentTableData = tableData[currentTableDataIndex]; let selected_class_i; let termsReset = {}; +// Counter for creating new assignments +var newAssignmentIDCounter = 0; + let tempCell; // When the user clicks anywhere outside of a modal or dropdown, close it window.addEventListener("click", function(event) { @@ -366,7 +369,9 @@ let assignmentsTable = new Tabulator("#assignmentsTable", { isNaN(cell.getRow().getData().score) || currentTableData.currentTermData .classes[selected_class_i] - .assignments[cell.getRow().getPosition()].synthetic + .assignments.filter(value => { + return !(value["placeholder"] || false) + })[cell.getRow().getPosition()].synthetic ) ? "" : '', width: 40, align: "center", @@ -587,7 +592,7 @@ let assignmentsTable = new Tabulator("#assignmentsTable", { new Snackbar(`You deleted ${data["name"]}`, { color: "var(--red1)", textColor: "var(--white)", - buttonText: "Undo?", + buttonText: "Undo", buttonClick: () => replaceAssignmentFromID({ assignment_id: data["assignment_id"], placeholder: true }, data, selected_class_i), timeout: 7500, timeoutFunction: () => removeAssignmentFromID(data["assignment_id"], selected_class_i), @@ -1224,7 +1229,7 @@ class Snackbar { * timeoutFunction: Function - What to run on timeout (doesn't run if hidden or destroyed) * timeoutMode: can be "destroy", "hide", "none" or empty. Determines what to do on timeout, destroys by default */ - constructor(text, options) { + constructor(text, options = {}) { this.text = text; this.color = options["color"]; this.textColor = options["textColor"]; From 4cbc8010e5bae80a9c5d6b1e1a65e15a9b4d5239 Mon Sep 17 00:00:00 2001 From: andOrlando <59105749+andOrlando@users.noreply.github.com> Date: Sat, 26 Dec 2020 12:47:26 -0500 Subject: [PATCH 10/17] Update public/js/home.js I don't know how to get rid of this suggestion otherwise so I'll cave Co-authored-by: psvenk <45520974+psvenk@users.noreply.github.com> --- public/js/home.js | 1 - 1 file changed, 1 deletion(-) diff --git a/public/js/home.js b/public/js/home.js index d0a93287..8f492cde 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -1212,7 +1212,6 @@ document.getElementById("sidenav-overlay").addEventListener("click", closeSideNa class Snackbar { - static snackbars = {}; static snackbarIDs = []; From fc156ef0e5e42a935d1e4c3623e93d014e986bcb Mon Sep 17 00:00:00 2001 From: andOrlando <59105749+andOrlando@users.noreply.github.com> Date: Wed, 6 Jan 2021 19:01:04 -0500 Subject: [PATCH 11/17] moves snackbar code to another file --- public/home.html | 1 + public/js/home.js | 232 ------------------------------------------ public/js/snackbar.js | 230 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 232 deletions(-) create mode 100644 public/js/snackbar.js diff --git a/public/home.html b/public/home.html index 877d6708..a748ade8 100644 --- a/public/home.html +++ b/public/home.html @@ -32,6 +32,7 @@ + diff --git a/public/js/home.js b/public/js/home.js index 8f492cde..1740f4e1 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -1210,238 +1210,6 @@ function closeSideNav() { // Allows exiting sidenav by clicking anywhere outside document.getElementById("sidenav-overlay").addEventListener("click", closeSideNav); - -class Snackbar { - static snackbars = {}; - static snackbarIDs = []; - - /** - * text is the main requirement, and it's just text - * color: String - string reference to a color or a variable, sets the background color - * textColor: String - string reference to a color or a variable, sets the text color - * buttonText: String - Sets the button text, both it and buttonClick have to be defined for the button to show - * buttonClick: Function - Sets the button's onclick logic, both it and buttonText have to be defined for the button to show - * destroyWhenButtonClicked : Boolean - Whether or not it should destroy itself when the button is clicked, defaults to true - * bodyClick: Function - Sets the body's onclick logic - * destroyWhenBodyClicked : Boolean - Whether or not it should destroy itself when the body is clicked, defaults to true - * timeout: Int - Time in ms - * timeoutFunction: Function - What to run on timeout (doesn't run if hidden or destroyed) - * timeoutMode: can be "destroy", "hide", "none" or empty. Determines what to do on timeout, destroys by default - */ - constructor(text, options = {}) { - this.text = text; - this.color = options["color"]; - this.textColor = options["textColor"]; - this.buttonText = options["buttonText"]; - this.buttonClick = options["buttonClick"]; - this.destroyWhenButtonClicked = options["destroyWhenButtonClicked"] || true; - this.bodyClick = options["bodyClick"]; - this.destroyWhenBodyClicked = options["destroyWhenBodyClicked"] || true; - - //timeout logic - this.timeoutFunction = options["timeoutFunction"] !== undefined ? options["timeoutFunction"] : () => {}; - this.timeout = options["timeout"]; - this.timeoutInProgress; - - //what to run on timeout - this.timeoutEndFunction; - switch(options["timeoutMode"]) { - case "destroy": - case undefined: - this.timeoutEndFunction = () => this.destroy(); - break; - case "hide": - this.timeoutEndFunction = () => this.hide(); - break; - case "none": - this.timeoutEndFunction = () => {}; - break; - } - - //creates this.id - this.id; - } - - /** - * creates the snackbar in the HTML - * if you want to show it as soon as you make it, use show without calling make instead - * returns the snackbar object - */ - make() { - //stops if the element already exists - if (typeof document.getElementById(`sidenav-${this.id}`) === undefined) { - return; - } - - //gives it an ID if it doesn't already have one - if (this.id === undefined) { - this.createID(); - } - - //creates the snackbar and gives it classes - const snackbarNode = document.createElement("DIV"); - snackbarNode.classList.add("snackbar"); - snackbarNode.classList.add("hidden"); - - //assigns it id based off of it's actual id - snackbarNode.id = `snackbar-${this.id}`; - - //adds color if given - if (this.color !== undefined) { - snackbarNode.style.backgroundColor = `${this.color}`; - } - - //sets the body onclick listener which just destroys it by default - const bodyOnClickFunction = this.bodyClick !== undefined ? () => this.bodyClick() : () => {}; - const destroyFromBody = this.destroyWhenBodyClicked ? () => this.destroy() : () => {}; - snackbarNode.addEventListener("click", () => { - bodyOnClickFunction(); - destroyFromBody(); - }) - - //adds the text - const textNode = document.createElement("SPAN"); - textNode.textContent = this.text; - - //colors the text if necessary - if (this.textColor !== undefined) { - textNode.style.color = this.textColor; - } - - //adds the text node - snackbarNode.appendChild(textNode); - - //makes the button if given button parameters - if (this.buttonText !== undefined && this.buttonClick != undefined) { - //creates the button and adds class - const buttonNode = document.createElement("BUTTON"); - - //creates the text span - const buttonTextNode = document.createElement("SPAN"); - buttonTextNode.textContent = this.buttonText; - - //colors the text if necessary - if (this.textColor !== undefined) { - buttonTextNode.style.color = this.textColor; - } - - if (this.color !== undefined) { - buttonNode.style.backgroundColor = this.color; - } - - //adds the text node - buttonNode.appendChild(buttonTextNode); - - //sets the button onclick listener which runs the given funtion and destroys the snackar by default - const destroyFromButton = this.destroyWhenButtonClicked ? () => this.destroy() : () => {}; - buttonNode.addEventListener("click", event => { - this.buttonClick(); - destroyFromButton(); - //stops propogation so the body event isn't called - event.stopPropagation(); - }) - - snackbarNode.appendChild(buttonNode); - } - - //adds the node to the body, puts reference to DOM element in this.element - document.body.appendChild(snackbarNode); - this.element = document.getElementById(`snackbar-${this.id}`); - - return this; - } - - /** - * shows the snackbar - * if it's not already made, makes it - * if you need to show right after making, use this - * returns the snackbar object - */ - show() { - //starts the timeout - if (this.timeout !== undefined) { - this.timeoutInProgress = setTimeout(() => { - this.timeoutFunction(); - this.timeoutEndFunction(); - this.timeoutInProgress = undefined; //resets the timeoutInProgress variable at the end of the timeout - }, this.timeout); - } - - const removeHidden = () => this.element.classList.remove("hidden"); - - //if not already made, makes the snackbar - if (document.getElementById(`snackbar-${this.id}`) === null) { - this.make(); - //waits a momement to make sure the snackbar's animation functions properly - setTimeout(removeHidden, 10); - return this; - } else { - removeHidden(); - return this; - } - } - - /** - * hides the snackbar - * returns the snackbar object - */ - hide() { - if (this.timeoutInProgress !== undefined) { - clearTimeout(this.timeoutInProgress); - this.timeoutInProgress = undefined; - } - - this.element.classList.add("hidden"); - return this; - } - - /** - * destroys the snackbar, its references and its ID - * if it's not already hidden, hides it unless override is true - */ - destroy() { - const snackbar = this; - - //function which deletes the references and ids - const finalizeDeletion = function() { - snackbar.element.remove(); - delete Snackbar.snackbarIDs[Snackbar.snackbarIDs.indexOf(snackbar.id)]; - delete Snackbar.snackbars[snackbar.id]; - snackbar.id = undefined; - } - - //if it's not hidden it shouldn't just dissapear - if (this.element.classList.contains("hidden")) { - finalizeDeletion(); - } else { - this.hide(); - //deletes it as soon as it's actually hidden - setTimeout(finalizeDeletion, 250); - } - } - - /** - * creates and reserves the ID for this snackbar - * also creates its reference in snackbars - */ - createID() { - let id = null; - - //goes through all consecutive numbers to find an id - let iterator = 0 - while (id === null) { - //checks if the id already exists, otherwise continues to iterate - Snackbar.snackbarIDs.includes(iterator) ? iterator++ : id = iterator; - } - - Snackbar.snackbarIDs.push(id); - Snackbar.snackbars[id] = this; - this.id = id; - - return id; - } -} - $("#export_button").click(() => { prefs = {}; diff --git a/public/js/snackbar.js b/public/js/snackbar.js new file mode 100644 index 00000000..3ae9215f --- /dev/null +++ b/public/js/snackbar.js @@ -0,0 +1,230 @@ +class Snackbar { + static snackbars = {}; + static snackbarIDs = []; + + /** + * text is the main requirement, and it's just text + * color: String - string reference to a color or a variable, sets the background color + * textColor: String - string reference to a color or a variable, sets the text color + * buttonText: String - Sets the button text, both it and buttonClick have to be defined for the button to show + * buttonClick: Function - Sets the button's onclick logic, both it and buttonText have to be defined for the button to show + * destroyWhenButtonClicked : Boolean - Whether or not it should destroy itself when the button is clicked, defaults to true + * bodyClick: Function - Sets the body's onclick logic + * destroyWhenBodyClicked : Boolean - Whether or not it should destroy itself when the body is clicked, defaults to true + * timeout: Int - Time in ms + * timeoutFunction: Function - What to run on timeout (doesn't run if hidden or destroyed) + * timeoutMode: can be "destroy", "hide", "none" or empty. Determines what to do on timeout, destroys by default + */ + constructor(text, options = {}) { + this.text = text; + this.color = options["color"]; + this.textColor = options["textColor"]; + this.buttonText = options["buttonText"]; + this.buttonClick = options["buttonClick"]; + this.destroyWhenButtonClicked = options["destroyWhenButtonClicked"] || true; + this.bodyClick = options["bodyClick"]; + this.destroyWhenBodyClicked = options["destroyWhenBodyClicked"] || true; + + //timeout logic + this.timeoutFunction = options["timeoutFunction"] !== undefined ? options["timeoutFunction"] : () => {}; + this.timeout = options["timeout"]; + this.timeoutInProgress; + + //what to run on timeout + this.timeoutEndFunction; + switch(options["timeoutMode"]) { + case "destroy": + case undefined: + this.timeoutEndFunction = () => this.destroy(); + break; + case "hide": + this.timeoutEndFunction = () => this.hide(); + break; + case "none": + this.timeoutEndFunction = () => {}; + break; + } + + //creates this.id + this.id; + } + + /** + * creates the snackbar in the HTML + * if you want to show it as soon as you make it, use show without calling make instead + * returns the snackbar object + */ + make() { + //stops if the element already exists + if (typeof document.getElementById(`sidenav-${this.id}`) === undefined) { + return; + } + + //gives it an ID if it doesn't already have one + if (this.id === undefined) { + this.createID(); + } + + //creates the snackbar and gives it classes + const snackbarNode = document.createElement("DIV"); + snackbarNode.classList.add("snackbar"); + snackbarNode.classList.add("hidden"); + + //assigns it id based off of it's actual id + snackbarNode.id = `snackbar-${this.id}`; + + //adds color if given + if (this.color !== undefined) { + snackbarNode.style.backgroundColor = `${this.color}`; + } + + //sets the body onclick listener which just destroys it by default + const bodyOnClickFunction = this.bodyClick !== undefined ? () => this.bodyClick() : () => {}; + const destroyFromBody = this.destroyWhenBodyClicked ? () => this.destroy() : () => {}; + snackbarNode.addEventListener("click", () => { + bodyOnClickFunction(); + destroyFromBody(); + }) + + //adds the text + const textNode = document.createElement("SPAN"); + textNode.textContent = this.text; + + //colors the text if necessary + if (this.textColor !== undefined) { + textNode.style.color = this.textColor; + } + + //adds the text node + snackbarNode.appendChild(textNode); + + //makes the button if given button parameters + if (this.buttonText !== undefined && this.buttonClick != undefined) { + //creates the button and adds class + const buttonNode = document.createElement("BUTTON"); + + //creates the text span + const buttonTextNode = document.createElement("SPAN"); + buttonTextNode.textContent = this.buttonText; + + //colors the text if necessary + if (this.textColor !== undefined) { + buttonTextNode.style.color = this.textColor; + } + + if (this.color !== undefined) { + buttonNode.style.backgroundColor = this.color; + } + + //adds the text node + buttonNode.appendChild(buttonTextNode); + + //sets the button onclick listener which runs the given funtion and destroys the snackar by default + const destroyFromButton = this.destroyWhenButtonClicked ? () => this.destroy() : () => {}; + buttonNode.addEventListener("click", event => { + this.buttonClick(); + destroyFromButton(); + //stops propogation so the body event isn't called + event.stopPropagation(); + }) + + snackbarNode.appendChild(buttonNode); + } + + //adds the node to the body, puts reference to DOM element in this.element + document.body.appendChild(snackbarNode); + this.element = document.getElementById(`snackbar-${this.id}`); + + return this; + } + + /** + * shows the snackbar + * if it's not already made, makes it + * if you need to show right after making, use this + * returns the snackbar object + */ + show() { + //starts the timeout + if (this.timeout !== undefined) { + this.timeoutInProgress = setTimeout(() => { + this.timeoutFunction(); + this.timeoutEndFunction(); + this.timeoutInProgress = undefined; //resets the timeoutInProgress variable at the end of the timeout + }, this.timeout); + } + + const removeHidden = () => this.element.classList.remove("hidden"); + + //if not already made, makes the snackbar + if (document.getElementById(`snackbar-${this.id}`) === null) { + this.make(); + //waits a momement to make sure the snackbar's animation functions properly + setTimeout(removeHidden, 10); + return this; + } else { + removeHidden(); + return this; + } + } + + /** + * hides the snackbar + * returns the snackbar object + */ + hide() { + if (this.timeoutInProgress !== undefined) { + clearTimeout(this.timeoutInProgress); + this.timeoutInProgress = undefined; + } + + this.element.classList.add("hidden"); + return this; + } + + /** + * destroys the snackbar, its references and its ID + * if it's not already hidden, hides it unless override is true + */ + destroy() { + const snackbar = this; + + //function which deletes the references and ids + const finalizeDeletion = function() { + snackbar.element.remove(); + delete Snackbar.snackbarIDs[Snackbar.snackbarIDs.indexOf(snackbar.id)]; + delete Snackbar.snackbars[snackbar.id]; + snackbar.id = undefined; + } + + //if it's not hidden it shouldn't just dissapear + if (this.element.classList.contains("hidden")) { + finalizeDeletion(); + } else { + this.hide(); + //deletes it as soon as it's actually hidden + setTimeout(finalizeDeletion, 250); + } + } + + /** + * creates and reserves the ID for this snackbar + * also creates its reference in snackbars + */ + createID() { + let id = null; + + //goes through all consecutive numbers to find an id + let iterator = 0 + while (id === null) { + //checks if the id already exists, otherwise continues to iterate + Snackbar.snackbarIDs.includes(iterator) ? iterator++ : id = iterator; + } + + Snackbar.snackbarIDs.push(id); + Snackbar.snackbars[id] = this; + this.id = id; + + return id; + } +} \ No newline at end of file From 2a0fa3454529e3990109a359ff24bd9daa4da7ef Mon Sep 17 00:00:00 2001 From: andOrlando <59105749+andOrlando@users.noreply.github.com> Date: Mon, 18 Jan 2021 12:19:01 -0500 Subject: [PATCH 12/17] adds an undo feature with ctrl+z -fixes a random tooltips bug for some reason -changes the ::after to ::before for no reason -adds an undo-registry to keep track of all the undos -adds states to make it easier to ascertain the status of a given snackbar -slightly improves a couple comments --- public/css/home.css | 4 ++-- public/js/buttonFunctions.js | 2 ++ public/js/home.js | 30 +++++++++++++++++++++++++-- public/js/snackbar.js | 40 +++++++++++++++++++++++++++++++++--- 4 files changed, 69 insertions(+), 7 deletions(-) diff --git a/public/css/home.css b/public/css/home.css index 7d8bf86d..3b740163 100644 --- a/public/css/home.css +++ b/public/css/home.css @@ -1376,7 +1376,7 @@ hr { order: 2; } -.snackbar button::after { +.snackbar button::before { content: ''; height: 100%; width: 100%; @@ -1390,7 +1390,7 @@ hr { transition: opacity 100ms ease-in; } -.snackbar button:hover::after { +.snackbar button:hover::before { opacity: 1; } diff --git a/public/js/buttonFunctions.js b/public/js/buttonFunctions.js index 9ee86a3e..787ce12f 100755 --- a/public/js/buttonFunctions.js +++ b/public/js/buttonFunctions.js @@ -304,6 +304,8 @@ let updateGradePage = function() { $(".gpa_select-selected").html("Quarter GPA: " + GPA.percent); $("#" + currentTerm).html("Quarter GPA: " + GPA.percent); } + + setup_tooltips(); } let exportTableData = async function(prefs) { diff --git a/public/js/home.js b/public/js/home.js index 1740f4e1..57a39960 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -39,6 +39,23 @@ let termsReset = {}; // Counter for creating new assignments var newAssignmentIDCounter = 0; +// Registry for undos, contains assignment ID and the snackbar that corresponds to it +// contains all the undo snackbars +const undoRegistry = [] + +document.onkeydown = e => { + var evtobj = window.event || e + if (evtobj.keyCode == 90 && evtobj.ctrlKey && undoRegistry.length !== 0) { + console.log(`list: ${undoRegistry}`) + console.log(`state: ${undoRegistry[0].state}`) + if (undoRegistry[0].state == Snackbar.SHOWN) { + undoRegistry[0].destroy(); + } + undoRegistry[0].buttonClick(); + } +} + + let tempCell; // When the user clicks anywhere outside of a modal or dropdown, close it window.addEventListener("click", function(event) { @@ -586,18 +603,27 @@ let assignmentsTable = new Tabulator("#assignmentsTable", { width: 40, align: "center", cellClick: function(e, cell) { + const data = cell.getRow().getData() replaceAssignmentFromID(data, {assignment_id: data["assignment_id"], placeholder: true}, selected_class_i); - new Snackbar(`You deleted ${data["name"]}`, { + const undoSnackbar = new Snackbar(`You deleted ${data["name"]}`, { color: "var(--red1)", textColor: "var(--white)", buttonText: "Undo", - buttonClick: () => replaceAssignmentFromID({ assignment_id: data["assignment_id"], placeholder: true }, data, selected_class_i), + //buttonclick replaces the assignment with a placeholder that just contains the assignemnt ID + buttonClick: () => { + replaceAssignmentFromID({ assignment_id: data["assignment_id"], placeholder: true }, data, selected_class_i) + undoRegistry.splice(undoRegistry.indexOf(undoSnackbar), 1) + }, timeout: 7500, + //on either timeout or bodyclick (basically just when its dismissed) deletes the placeholder assignment timeoutFunction: () => removeAssignmentFromID(data["assignment_id"], selected_class_i), bodyClick: () => removeAssignmentFromID(data["assignment_id"], selected_class_i), }).show(); + + undoRegistry.push(undoSnackbar) + }, headerSort: false, cssClass: "icon-col allow-overflow" diff --git a/public/js/snackbar.js b/public/js/snackbar.js index 3ae9215f..7fdcebba 100644 --- a/public/js/snackbar.js +++ b/public/js/snackbar.js @@ -1,7 +1,29 @@ +/** snackbar.js + * Has all the snackbar class code in it + * To use the snackbar, just give it some text, give it some options (or not) and .show() it. + * It goes a little more into detail in the descriptions of each function. It also has a nice + * little jsdoc thing if you're using something that supports it. If not, sucks for you. + */ class Snackbar { + + /** + * snackbars contains key value pairs of snackbar IDs and snackbar references respectively + * if you ever need to get a snackbar from an ID + * snackbarIDs makes sure the IDs are unique + */ static snackbars = {}; static snackbarIDs = []; + /** + * state IDs + * DESTROYED means it doesn't exist in html + * HIDDEN means it exists but is hdiden + * SHOWN means it exists and is shown + */ + static DESTROYED = 0 + static HIDDEN = 1 + static SHOWN = 2 + /** * text is the main requirement, and it's just text * color: String - string reference to a color or a variable, sets the background color @@ -47,6 +69,10 @@ class Snackbar { //creates this.id this.id; + + //sets state to destroyed + this.state = Snackbar.DESTROYED + } /** @@ -61,6 +87,7 @@ class Snackbar { } //gives it an ID if it doesn't already have one + //this can happen if the snackbar object still exists but has been destroyed if (this.id === undefined) { this.createID(); } @@ -70,12 +97,12 @@ class Snackbar { snackbarNode.classList.add("snackbar"); snackbarNode.classList.add("hidden"); - //assigns it id based off of it's actual id + //assigns its id based off of it's actual id snackbarNode.id = `snackbar-${this.id}`; //adds color if given if (this.color !== undefined) { - snackbarNode.style.backgroundColor = `${this.color}`; + snackbarNode.style.backgroundColor = this.color; } //sets the body onclick listener which just destroys it by default @@ -135,6 +162,7 @@ class Snackbar { document.body.appendChild(snackbarNode); this.element = document.getElementById(`snackbar-${this.id}`); + this.state = Snackbar.HIDDEN return this; } @@ -154,7 +182,10 @@ class Snackbar { }, this.timeout); } - const removeHidden = () => this.element.classList.remove("hidden"); + const removeHidden = () => { + this.state = Snackbar.SHOWN + this.element.classList.remove("hidden"); + } //if not already made, makes the snackbar if (document.getElementById(`snackbar-${this.id}`) === null) { @@ -179,6 +210,7 @@ class Snackbar { } this.element.classList.add("hidden"); + this.state = Snackbar.HIDDEN return this; } @@ -197,6 +229,8 @@ class Snackbar { snackbar.id = undefined; } + this.state = Snackbar.DESTROYED + //if it's not hidden it shouldn't just dissapear if (this.element.classList.contains("hidden")) { finalizeDeletion(); From 7596c94ad603ac5b8464c9a3c0e8f2f3d9982212 Mon Sep 17 00:00:00 2001 From: andOrlando <59105749+andOrlando@users.noreply.github.com> Date: Tue, 19 Jan 2021 09:10:26 -0500 Subject: [PATCH 13/17] removes console.logs I left them in for debugging... --- public/js/home.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/js/home.js b/public/js/home.js index 57a39960..a14b4938 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -46,8 +46,6 @@ const undoRegistry = [] document.onkeydown = e => { var evtobj = window.event || e if (evtobj.keyCode == 90 && evtobj.ctrlKey && undoRegistry.length !== 0) { - console.log(`list: ${undoRegistry}`) - console.log(`state: ${undoRegistry[0].state}`) if (undoRegistry[0].state == Snackbar.SHOWN) { undoRegistry[0].destroy(); } From be3558cee8cc75d6912908f6fa3781b3bfb5ebe7 Mon Sep 17 00:00:00 2001 From: andOrlando Date: Tue, 16 Mar 2021 11:09:26 -0400 Subject: [PATCH 14/17] Fixes snackbar bug Rejoice! After a whole month or something I finally fixed that one bug! (changed method of keeping track of snackbar stuff) --- public/js/home.js | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/public/js/home.js b/public/js/home.js index a14b4938..dc060a58 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -41,15 +41,17 @@ var newAssignmentIDCounter = 0; // Registry for undos, contains assignment ID and the snackbar that corresponds to it // contains all the undo snackbars -const undoRegistry = [] +const undoData = [] document.onkeydown = e => { var evtobj = window.event || e - if (evtobj.keyCode == 90 && evtobj.ctrlKey && undoRegistry.length !== 0) { - if (undoRegistry[0].state == Snackbar.SHOWN) { - undoRegistry[0].destroy(); - } - undoRegistry[0].buttonClick(); + if (evtobj.keyCode == 90 && evtobj.ctrlKey && undoData.length !== 0) { + if (undoData[0].Snackbar !== undefined) { + undoData[0].Snackbar.destroy(); + undoData[0].Snackbar = undefined; + } + replaceAssignmentFromID({assignment_id: undoData[0].assignment_id, placeholder: true}, undoData[0], undoData[0].selected_class_i); + undoData.shift(); } } @@ -602,26 +604,32 @@ let assignmentsTable = new Tabulator("#assignmentsTable", { align: "center", cellClick: function(e, cell) { - const data = cell.getRow().getData() + const data = cell.getRow().getData(); replaceAssignmentFromID(data, {assignment_id: data["assignment_id"], placeholder: true}, selected_class_i); + const undoSnackbar = new Snackbar(`You deleted ${data["name"]}`, { color: "var(--red1)", textColor: "var(--white)", buttonText: "Undo", //buttonclick replaces the assignment with a placeholder that just contains the assignemnt ID buttonClick: () => { - replaceAssignmentFromID({ assignment_id: data["assignment_id"], placeholder: true }, data, selected_class_i) - undoRegistry.splice(undoRegistry.indexOf(undoSnackbar), 1) + //gets index for splicing and comparing + index = undoData.map(arrData => arrData.assignment_id).indexOf(data.assignment_id); + arrData = undoData[index]; //undo data at the index + arrData.Snackbar = undefined; //removes snackbar before putting data back + replaceAssignmentFromID({assignment_id: arrData.assignment_id, placeholder: true}, arrData, arrData.selected_class_i); + undoData.splice(index, 1); }, timeout: 7500, - //on either timeout or bodyclick (basically just when its dismissed) deletes the placeholder assignment - timeoutFunction: () => removeAssignmentFromID(data["assignment_id"], selected_class_i), - bodyClick: () => removeAssignmentFromID(data["assignment_id"], selected_class_i), + //on either a timeout or a bodyclick removes the snackbar link + timeoutFunction: () => undoData[undoData.map(arrData => arrData.assignment_id).indexOf(data.assignment_id)].Snackbar = undefined, + bodyClick: () => undoData[undoData.map(arrData => arrData.assignment_id).indexOf(data.assignment_id)].Snackbar = undefined, }).show(); - undoRegistry.push(undoSnackbar) - + data.Snackbar = undoSnackbar; + data.selected_class_i = selected_class_i; + undoData.push(data); }, headerSort: false, cssClass: "icon-col allow-overflow" From ac3a4b5dc80a2e88ef688140cebeb1baacda6299 Mon Sep 17 00:00:00 2001 From: andOrlando Date: Tue, 16 Mar 2021 11:35:34 -0400 Subject: [PATCH 15/17] Fixes undo order I was using unshift instead of shift which wasn't great, the new order feels much more intuitive --- public/js/home.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/home.js b/public/js/home.js index dc060a58..4de04f73 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -629,7 +629,7 @@ let assignmentsTable = new Tabulator("#assignmentsTable", { data.Snackbar = undoSnackbar; data.selected_class_i = selected_class_i; - undoData.push(data); + undoData.unshift(data); }, headerSort: false, cssClass: "icon-col allow-overflow" From 0258d6c0206ffd0aa780690d46f902af1bfc4066 Mon Sep 17 00:00:00 2001 From: psvenk <45520974+psvenk@users.noreply.github.com> Date: Wed, 17 Mar 2021 17:11:56 -0400 Subject: [PATCH 16/17] Normalize whitespace and fix spelling --- public/js/home.js | 4 ++-- public/js/snackbar.js | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/public/js/home.js b/public/js/home.js index 4de04f73..04ddbc6a 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -603,7 +603,7 @@ let assignmentsTable = new Tabulator("#assignmentsTable", { width: 40, align: "center", cellClick: function(e, cell) { - + const data = cell.getRow().getData(); replaceAssignmentFromID(data, {assignment_id: data["assignment_id"], placeholder: true}, selected_class_i); @@ -611,7 +611,7 @@ let assignmentsTable = new Tabulator("#assignmentsTable", { const undoSnackbar = new Snackbar(`You deleted ${data["name"]}`, { color: "var(--red1)", textColor: "var(--white)", - buttonText: "Undo", + buttonText: "Undo", //buttonclick replaces the assignment with a placeholder that just contains the assignemnt ID buttonClick: () => { //gets index for splicing and comparing diff --git a/public/js/snackbar.js b/public/js/snackbar.js index 7fdcebba..52948d9c 100644 --- a/public/js/snackbar.js +++ b/public/js/snackbar.js @@ -7,7 +7,7 @@ class Snackbar { /** - * snackbars contains key value pairs of snackbar IDs and snackbar references respectively + * snackbars contains key value pairs of snackbar IDs and snackbar references respectively * if you ever need to get a snackbar from an ID * snackbarIDs makes sure the IDs are unique */ @@ -17,7 +17,7 @@ class Snackbar { /** * state IDs * DESTROYED means it doesn't exist in html - * HIDDEN means it exists but is hdiden + * HIDDEN means it exists but is hidden * SHOWN means it exists and is shown */ static DESTROYED = 0 @@ -33,7 +33,7 @@ class Snackbar { * destroyWhenButtonClicked : Boolean - Whether or not it should destroy itself when the button is clicked, defaults to true * bodyClick: Function - Sets the body's onclick logic * destroyWhenBodyClicked : Boolean - Whether or not it should destroy itself when the body is clicked, defaults to true - * timeout: Int - Time in ms + * timeout: Int - Time in ms * timeoutFunction: Function - What to run on timeout (doesn't run if hidden or destroyed) * timeoutMode: can be "destroy", "hide", "none" or empty. Determines what to do on timeout, destroys by default */ @@ -181,7 +181,7 @@ class Snackbar { this.timeoutInProgress = undefined; //resets the timeoutInProgress variable at the end of the timeout }, this.timeout); } - + const removeHidden = () => { this.state = Snackbar.SHOWN this.element.classList.remove("hidden"); @@ -199,7 +199,7 @@ class Snackbar { } } - /** + /** * hides the snackbar * returns the snackbar object */ @@ -261,4 +261,4 @@ class Snackbar { return id; } -} \ No newline at end of file +} From dfec4289220824d8923c0f8830acc432db9d23b5 Mon Sep 17 00:00:00 2001 From: psvenk <45520974+psvenk@users.noreply.github.com> Date: Wed, 24 Mar 2021 14:12:54 -0400 Subject: [PATCH 17/17] Normalize style Keep lines to <=80 characters, simplify some code, fix indentation --- .editorconfig | 2 +- public/js/buttonFunctions.js | 36 ++++++++-------- public/js/home.js | 82 +++++++++++++++++++++++------------- 3 files changed, 73 insertions(+), 47 deletions(-) diff --git a/.editorconfig b/.editorconfig index 6a85b4ce..a712f8a2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,7 +10,7 @@ trim_trailing_whitespace = true indent_style = space indent_size = 2 -[{serve.js,scrape.js,public/js/home.js,public/js/clock.js}] +[{serve.js,scrape.js,public/js/{home,clock,snackbar}.js}] indent_style = space indent_size = 4 diff --git a/public/js/buttonFunctions.js b/public/js/buttonFunctions.js index 787ce12f..a682a421 100755 --- a/public/js/buttonFunctions.js +++ b/public/js/buttonFunctions.js @@ -1,28 +1,29 @@ let newAssignment = function() { - currentTableData.currentTermData.classes[selected_class_i].edited = true; if (!isNaN(selected_class_i)) { - - currentTableData.currentTermData.classes[selected_class_i].assignments.unshift({ - "assignment_id": newAssignmentIDCounter.toString(), - "name": "Assignment", - "category": Object.keys(currentTableData.currentTermData.classes[selected_class_i].categories)[currentFilterRow >= 0 ? currentFilterRow : 0], - "score": 10, - "max_score": 10, - "percentage": 100, - "color": "green", - "synthetic": "true", - }); + currentTableData.currentTermData.classes[selected_class_i].assignments + .unshift({ + "assignment_id": newAssignmentIDCounter.toString(), + "name": "Assignment", + "category": Object.keys( + currentTableData.currentTermData.classes[selected_class_i].categories + )[currentFilterRow >= 0 ? currentFilterRow : 0], + "score": 10, + "max_score": 10, + "percentage": 100, + "color": "green", + "synthetic": "true", + }); newAssignmentIDCounter++; updateGradePage(); - } } function replaceAssignmentFromID(oldData, newData, classID) { - const assignments = currentTableData.currentTermData.classes[classID].assignments; + const assignments = currentTableData.currentTermData.classes[classID] + .assignments; const index = assignments.findIndex(({ assignment_id }) => assignment_id === oldData.assignment_id @@ -32,7 +33,8 @@ function replaceAssignmentFromID(oldData, newData, classID) { } function removeAssignmentFromID(id, classID) { - const assignments = currentTableData.currentTermData.classes[classID].assignments; + const assignments = currentTableData.currentTermData.classes[classID] + .assignments; const newArray = assignments.filter(({ assignment_id }) => assignment_id !== id ); @@ -149,8 +151,8 @@ let updateGradePage = function() { categoriesTable.setData(currentTableData.currentTermData.classes[selected_class_i].categoryDisplay); assignmentsTable.replaceData(currentTableData.currentTermData - .classes[selected_class_i].assignments - .filter(({ placeholder }) => !placeholder) + .classes[selected_class_i].assignments + .filter(({ placeholder }) => !placeholder) ); currentTableData.currentTermData.calcGPA = computeGPA(currentTableData.currentTermData.classes); diff --git a/public/js/home.js b/public/js/home.js index 04ddbc6a..47a23e09 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -43,17 +43,21 @@ var newAssignmentIDCounter = 0; // contains all the undo snackbars const undoData = [] -document.onkeydown = e => { +window.addEventListener("keydown", e => { var evtobj = window.event || e if (evtobj.keyCode == 90 && evtobj.ctrlKey && undoData.length !== 0) { if (undoData[0].Snackbar !== undefined) { undoData[0].Snackbar.destroy(); undoData[0].Snackbar = undefined; } - replaceAssignmentFromID({assignment_id: undoData[0].assignment_id, placeholder: true}, undoData[0], undoData[0].selected_class_i); + replaceAssignmentFromID( + { assignment_id: undoData[0].assignment_id, placeholder: true }, + undoData[0], + undoData[0].selected_class_i + ); undoData.shift(); } -} +}); let tempCell; @@ -386,9 +390,9 @@ let assignmentsTable = new Tabulator("#assignmentsTable", { isNaN(cell.getRow().getData().score) || currentTableData.currentTermData .classes[selected_class_i] - .assignments.filter(value => { - return !(value["placeholder"] || false) - })[cell.getRow().getPosition()].synthetic + .assignments.filter(value => + !value["placeholder"] + )[cell.getRow().getPosition()].synthetic ) ? "" : '', width: 40, align: "center", @@ -603,33 +607,53 @@ let assignmentsTable = new Tabulator("#assignmentsTable", { width: 40, align: "center", cellClick: function(e, cell) { - const data = cell.getRow().getData(); replaceAssignmentFromID(data, {assignment_id: data["assignment_id"], placeholder: true}, selected_class_i); + const undoSnackbar = new Snackbar( + `You deleted ${data["name"]}`, { + color: "var(--red1)", + textColor: "var(--white)", + buttonText: "Undo", + + // Replace the assignment with a placeholder that just + // contains the assignment ID + buttonClick: () => { + // Get index for splicing and comparing + index = undoData.findIndex(a => + a.assignment_id === data.assignment_id); + arrData = undoData[index]; + // Remove snackbar before putting data back + arrData.Snackbar = undefined; + replaceAssignmentFromID({ + assignment_id: arrData.assignment_id, + placeholder: true, + }, arrData, arrData.selected_class_i); + undoData.splice(index, 1); + }, + timeout: 7500, + + // On either a timeout or a bodyclick removes the + // snackbar link + timeoutFunction: () => { + undoData[ + undoData.map(arrData => arrData.assignment_id) + .indexOf(data.assignment_id) + ].Snackbar = undefined; + }, + + bodyClick: () => { + undoData[ + undoData.map(arrData => arrData.assignment_id) + .indexOf(data.assignment_id) + ].Snackbar = undefined; + }, + } + ).show(); - const undoSnackbar = new Snackbar(`You deleted ${data["name"]}`, { - color: "var(--red1)", - textColor: "var(--white)", - buttonText: "Undo", - //buttonclick replaces the assignment with a placeholder that just contains the assignemnt ID - buttonClick: () => { - //gets index for splicing and comparing - index = undoData.map(arrData => arrData.assignment_id).indexOf(data.assignment_id); - arrData = undoData[index]; //undo data at the index - arrData.Snackbar = undefined; //removes snackbar before putting data back - replaceAssignmentFromID({assignment_id: arrData.assignment_id, placeholder: true}, arrData, arrData.selected_class_i); - undoData.splice(index, 1); - }, - timeout: 7500, - //on either a timeout or a bodyclick removes the snackbar link - timeoutFunction: () => undoData[undoData.map(arrData => arrData.assignment_id).indexOf(data.assignment_id)].Snackbar = undefined, - bodyClick: () => undoData[undoData.map(arrData => arrData.assignment_id).indexOf(data.assignment_id)].Snackbar = undefined, - }).show(); - - data.Snackbar = undoSnackbar; - data.selected_class_i = selected_class_i; - undoData.unshift(data); + data.Snackbar = undoSnackbar; + data.selected_class_i = selected_class_i; + undoData.unshift(data); }, headerSort: false, cssClass: "icon-col allow-overflow"