--- /dev/null
+/**
+ * Generate a bootstrap modal
+ * Mandatory:
+ * @param {string} title - The modal title
+ * @param {string} body - HTML for the body
+ * @param {string} footer - HTML for the footer
+ *
+ * Optional:
+ * @param {string} size - the bootstrap size category for modals (sm, lg, xl)
+ * @param {boolean} static - whether or not to make the backdrop static, forcing the user to respond to the dialog
+ * @param {boolean} show - whether or not to automatically show the modal
+ * @returns {string} returns the ID
+ */
+
+function bsModal(title, body, footer, size = null, static = false, show = false)
+{
+ /* generate a random number between 1000 and 90000 to use as an id */
+ const min = 1000;
+ const max = 90000;
+ id = Date.now().toString(36); // base36 unique id
+ ourSize = "";
+ if (size)
+ ourSize += "modal-" + size;
+
+ const m1 = document.createElement("div");
+ const m2 = m1.cloneNode();
+ const m3 = m1.cloneNode();
+ const mHeader = m1.cloneNode();
+ const mBody = m1.cloneNode();
+ const mFooter = m1.cloneNode();
+
+ m1.classList.add("modal", "fade");
+ m1.id = id;
+ m1.role = "dialog";
+ m1.ariaHidden = "true";
+
+ m2.classList.add("modal-dialog", "modal-dialog-centered");
+ if (ourSize.length)
+ m2.classList.add(ourSize);
+
+ m2.role = "document";
+ m2.id = id + "-2";
+
+ m3.classList.add("modal-content");
+ m3.id = id + "-3";
+
+ mHeader.classList.add("modal-header");
+ mHeader.id = id + "-header";
+ mHeader.innerHTML =`<h5 class="modal-title" id="` + id + `"-title">` + title + `</h5>
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">×</span></button>`;
+
+ mBody.classList.add("modal-body");
+ mBody.id = id + "-body";
+ mBody.innerHTML = body;
+
+ mFooter.classList.add("modal-footer");
+ mFooter.id = id + "-footer";
+ mFooter.innerHTML = footer;
+
+ m1.appendChild(m2);
+ m2.appendChild(m3);
+ m3.appendChild(mHeader);
+ m3.appendChild(mBody);
+ m3.appendChild(mFooter);
+
+ document.body.append(m1);
+
+ if (static)
+ $('#' + m1.id).modal({ backdrop: "static" });
+
+ if (show)
+ $('#' + m1.id).modal('show');
+}
--- /dev/null
+
+/* Popup notifications */
+
+function generate_notif(title, body)
+{
+ /* generate a random number between 1000 and 90000 to use as an id */
+ const min = 1000;
+ const max = 90000;
+ const id = Math.floor(Math.random() * (max - min + 1)) + min;
+
+ const toast = document.createElement('div');
+ toast.classList.add('toast', 'hide');
+ toast.id = 'toast' + id;
+ toast.role = 'alert';
+ toast.ariaLive = 'assertive';
+ toast.ariaAtomic = 'true';
+ toast.setAttribute('data-delay', '10000');
+
+ const header = document.createElement('div');
+ header.classList.add('toast-header');
+
+ const theTitle = document.createElement('strong');
+ theTitle.classList.add('mr-auto');
+ theTitle.textContent = title;
+
+ const notiftime = document.createElement('div');
+ notiftime.classList.add('badge', 'rounded-pill', 'badge-primary', 'ml-1');
+ notiftime.textContent = 'Just now'; // always just now I think right :D
+
+ const closebutton = document.createElement('button');
+ closebutton.type = 'button';
+ closebutton.classList.add('ml-2', 'mb-1', 'close');
+ closebutton.setAttribute('data-dismiss', 'toast');
+ closebutton.ariaLabel = 'Close';
+
+ const closebuttonspan = document.createElement('span');
+ closebuttonspan.ariaHidden = 'true';
+ closebuttonspan.innerHTML = "×";
+
+ const toastbody = document.createElement('div');
+ toastbody.classList.add('toast-body');
+ toastbody.textContent = body;
+
+
+ /* put it all together */
+ closebutton.appendChild(closebuttonspan);
+ header.appendChild(theTitle);
+ header.appendChild(notiftime);
+ header.appendChild(closebutton);
+ toast.appendChild(header);
+ toast.appendChild(toastbody);
+ document.getElementById('toaster').append(toast);
+
+ $('#' + toast.id).toast('show');
+}