// Global variables const appPages = ['dashboard', 'settings']; const domRefs = {}; let timerId; const currentYear = new Date().getFullYear(); //Checks for internet connection status if (!navigator.onLine) notify( "There seems to be a problem connecting to the internet, Please check you internet connection.", "error", { sound: true } ); window.addEventListener("offline", () => { notify( "There seems to be a problem connecting to the internet, Please check you internet connection.", "error", { pinned: true, sound: true } ); }); window.addEventListener("online", () => { getRef("notification_drawer").clearAll(); notify("We are back online.", "success"); }); // Use instead of document.getElementById function getRef(elementId) { if (!domRefs.hasOwnProperty(elementId)) { domRefs[elementId] = { count: 1, ref: null, }; return document.getElementById(elementId); } else { if (domRefs[elementId].count < 3) { domRefs[elementId].count = domRefs[elementId].count + 1; return document.getElementById(elementId); } else { if (!domRefs[elementId].ref) domRefs[elementId].ref = document.getElementById(elementId); return domRefs[elementId].ref; } } } // returns dom with specified element function createElement(tagName, options) { const { className, textContent, innerHTML, attributes = {} } = options const elem = document.createElement(tagName) for (let attribute in attributes) { elem.setAttribute(attribute, attributes[attribute]) } if (className) elem.className = className if (textContent) elem.textContent = textContent if (innerHTML) elem.innerHTML = innerHTML return elem } // Use when a function needs to be executed after user finishes changes const debounce = (callback, wait) => { let timeoutId = null; return (...args) => { window.clearTimeout(timeoutId); timeoutId = window.setTimeout(() => { callback.apply(null, args); }, wait); }; } // Limits the rate of function execution function throttle(func, delay) { // If setTimeout is already scheduled, no need to do anything if (timerId) { return; } // Schedule a setTimeout after delay seconds timerId = setTimeout(function () { func(); // Once setTimeout function execution is finished, timerId = undefined so that in // the next scroll event function execution can be scheduled by the setTimeout timerId = undefined; }, delay); } // function required for popups or modals to appear function showPopup(popupId, pinned) { zIndex++ getRef(popupId).setAttribute('style', `z-index: ${zIndex}`) popupStack = getRef(popupId).show({ pinned, popupStack }) return getRef(popupId); } // hides the popup or modal function hidePopup() { if (popupStack.peek() === undefined) return; popupStack.peek().popup.hide() } // displays a popup for asking permission. Use this instead of JS confirm const getConfirmation = (title, message, cancelText = 'Cancel', confirmText = 'OK') => { return new Promise(resolve => { showPopup('confirmation_popup', true) getRef('confirm_title').textContent = title; getRef('confirm_message').textContent = message; let cancelButton = getRef('confirmation_popup').children[2].children[0], submitButton = getRef('confirmation_popup').children[2].children[1] submitButton.textContent = confirmText cancelButton.textContent = cancelText submitButton.onclick = () => { hidePopup() resolve(true); } cancelButton.onclick = () => { hidePopup() resolve(false); } }) } // displays a popup for asking user input. Use this instead of JS prompt async function getPromptInput(title, message = '', isPassword = true, cancelText = 'Cancel', confirmText = 'OK') { showPopup('prompt_popup', true) getRef('prompt_title').textContent = title; let input = getRef('prompt_input'); input.setAttribute("placeholder", message) let buttons = getRef('prompt_popup').querySelectorAll("sm-button"); if (isPassword) input.setAttribute("type", "text") else input.setAttribute("type", "password") input.focusIn() buttons[0].textContent = cancelText; buttons[1].textContent = confirmText; return new Promise((resolve, reject) => { buttons[0].onclick = () => { hidePopup() return; } buttons[1].onclick = () => { let value = input.value; hidePopup() resolve(value) } }) } //Function for displaying toast notifications. pass in error for mode param if you want to show an error. function notify(message, mode, options = {}) { let icon switch (mode) { case 'success': icon = `` break; case 'error': icon = `` options.pinned = true break; } getRef("notification_drawer").push(message, { icon, ...options }); if (mode === 'error') { console.error(message) } } function getFormatedTime(time, relative) { try { if (String(time).indexOf("_")) time = String(time).split("_")[0]; const intTime = parseInt(time); if (String(intTime).length < 13) time *= 1000; let timeFrag = new Date(intTime).toString().split(" "), day = timeFrag[0], month = timeFrag[1], date = timeFrag[2], year = timeFrag[3], minutes = new Date(intTime).getMinutes(), hours = new Date(intTime).getHours(), currentTime = new Date().toString().split(" "); minutes = minutes < 10 ? `0${minutes}` : minutes; let finalHours = ``; if (hours > 12) finalHours = `${hours - 12}:${minutes}`; else if (hours === 0) finalHours = `12:${minutes}`; else finalHours = `${hours}:${minutes}`; finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM`; if (relative) { return `${date} ${month} ${year}`; } else return `${finalHours} ${month} ${date} ${year}`; } catch (e) { console.error(e); return time; } } window.addEventListener('hashchange', e => showPage(window.location.hash)) window.addEventListener("load", () => { document.body.classList.remove('hide-completely') showPage(window.location.hash) // document.querySelectorAll('sm-input[data-flo-id]').forEach(input => input.customValidation = validateAddr) document.addEventListener('keyup', (e) => { if (e.code === 'Escape') { hidePopup() } }) document.addEventListener("pointerdown", (e) => { if (e.target.closest("button, sm-button:not([disabled]), .interact")) { createRipple(e, e.target.closest("button, sm-button, .interact")); } }); document.addEventListener('copy', () => { notify('copied', 'success') }) }); function createRipple(event, target) { const circle = document.createElement("span"); const diameter = Math.max(target.clientWidth, target.clientHeight); const radius = diameter / 2; const targetDimensions = target.getBoundingClientRect(); circle.style.width = circle.style.height = `${diameter}px`; circle.style.left = `${event.clientX - (targetDimensions.left + radius)}px`; circle.style.top = `${event.clientY - (targetDimensions.top + radius)}px`; circle.classList.add("ripple"); const rippleAnimation = circle.animate( [ { transform: "scale(3)", opacity: 0, }, ], { duration: 1000, fill: "forwards", easing: "ease-out", } ); target.append(circle); rippleAnimation.onfinish = () => { circle.remove(); }; } function showPage(targetPage, options = {}) { const { firstLoad, hashChange } = options let pageId if (targetPage === '') { pageId = 'overview_page' } else { pageId = targetPage.includes('#') ? targetPage.split('#')[1] : targetPage } if (!appPages.includes(pageId)) return document.querySelector('.page:not(.hide-completely)').classList.add('hide-completely') document.querySelector('.nav-list__item--active').classList.remove('nav-list__item--active') getRef(pageId).classList.remove('hide-completely') getRef(pageId).animate([ { opacity: 0, transform: 'translateX(-1rem)' }, { opacity: 1, transform: 'none' }, ], { duration: 300, easing: 'ease' }) const targetListItem = document.querySelector(`.nav-list__item[href="#${pageId}"]`) targetListItem.classList.add('nav-list__item--active') if (firstLoad && window.innerWidth > 640 && targetListItem.getBoundingClientRect().top > getRef('side_nav').getBoundingClientRect().height) { getRef('side_nav').scrollTo({ top: (targetListItem.getBoundingClientRect().top - getRef('side_nav').getBoundingClientRect().top + getRef('side_nav').scrollTop), behavior: 'smooth' }) } if (hashChange && window.innerWidth < 640) { getRef('side_nav').close() } } function buttonLoader(id, show) { const button = typeof id === 'string' ? getRef(id) : id; button.disabled = show; const animOptions = { duration: 200, fill: 'forwards', easing: 'ease' } if (show) { button.animate([ { clipPath: 'circle(100%)', }, { clipPath: 'circle(0)', }, ], animOptions).onfinish = e => { e.target.commitStyles() e.target.cancel() } button.parentNode.append(createElement('sm-spinner')) } else { button.style = '' const potentialTarget = button.parentNode.querySelector('sm-spinner') if (potentialTarget) potentialTarget.remove(); } }