diff --git a/main_UI.js b/main_UI.js index 1325565..7d17d72 100644 --- a/main_UI.js +++ b/main_UI.js @@ -149,9 +149,17 @@ window.addEventListener('online', () => { let zIndex = 50 // function required for popups or modals to appear function openPopup(popupId, pinned) { + if (popupStack.peek() === undefined) { + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + closePopup() + } + }) + } zIndex++ getRef(popupId).setAttribute('style', `z-index: ${zIndex}`) - return getRef(popupId).show({ pinned }) + getRef(popupId).show({ pinned }) + return getRef(popupId); } // hides the popup or modal @@ -170,14 +178,19 @@ document.addEventListener('popupopened', async e => { }) document.addEventListener('popupclosed', e => { + zIndex--; switch (e.target.id) { case '': break; } - if (popupStack.items.length === 0) { + if (popupStack.peek() === undefined) { // if there are no more popups, do something + document.removeEventListener('keydown', (e) => { + if (e.key === 'Escape') { + closePopup() + } + }) } - zIndex--; }) window.addEventListener('popstate', e => { if (!e.state) return @@ -540,19 +553,20 @@ async function routeTo(targetPage, options = {}) { appState.openedPages.add(pageId) } -// class based lazy loading +/** + * @class LazyLoader - class for lazy loading elements + * @constructor {object} options - options for the lazy loader + * @param {string} container - container element for the lazy loader + * @param {array} elementsToRender - array of elements to be rendered + * @param {function} renderFn - function to render the elements + * @param {object} options - options for the lazy loader + * @param {number} options.batchSize - number of elements to be rendered at a time + * @param {function} options.freshRender - function to be called when elements are rendered for first time + * @param {boolean} options.bottomFirst - if true, elements will be rendered from bottom to top + * @param {function} options.onEnd - function to be called when all elements are rendered + * @returns {LazyLoader} - instance of LazyLoader + */ class LazyLoader { - /** - * @constructor {object} options - options for the lazy loader - * @param {string} container - container for the lazy loader - * @param {array} elementsToRender - elements to be rendered - * @param {function} renderFn - function to be called for rendering each element - * @param {object} options - options for the lazy loader - * @param {number} options.batchSize - number of elements to be rendered at a time - * @param {function} options.freshRender - function to be called when elements are rendered for first time - * @param {boolean} options.bottomFirst - if true, elements will be rendered from bottom to top - * @param {function} options.onEnd - function to be called when all elements are rendered - */ constructor(container, elementsToRender, renderFn, options = {}) { const { batchSize = 10, freshRender, bottomFirst = false, onEnd } = options @@ -744,6 +758,7 @@ const slideOutUp = [ }, ] /** + * shows a child element of a parent element with animation * @param {HTMLElement} id - parent element * @param {number} index - index of child element to be shown * @param {object} options - options for the animation diff --git a/main_UI.min.js b/main_UI.min.js new file mode 100644 index 0000000..7cd8f47 --- /dev/null +++ b/main_UI.min.js @@ -0,0 +1 @@ +!function(e,t){var o=o||{};"function"==typeof o&&o.amd?o([],t):"object"==typeof exports&&"object"==typeof module?module.exports=t():"object"==typeof exports?exports.RelativeTime=t():e.RelativeTime=t()}(this,(function(){const e={year:31536e6,month:2628e6,day:864e5,hour:36e5,minute:6e4,second:1e3},o={numeric:"auto"};function n(e){e={locale:(e=e||{}).locale||"en",options:{...o,...e.options}},this.rtf=new Intl.RelativeTimeFormat(e.locale,e.options)}return n.prototype={from(t,o){const n=t-(o||new Date);for(let t in e)if(Math.abs(n)>e[t]||"second"==t)return this.rtf.format(Math.round(n/e[t]),t)}},n}));const relativeTime=new RelativeTime({style:"narrow"}),domRefs={},uiGlobals={};let timerId;const uiUtils={memoRef:elementId=>domRefs.hasOwnProperty(elementId)?domRefs[elementId].count<3?(domRefs[elementId].count=domRefs[elementId].count+1,document.getElementById(elementId)):(domRefs[elementId].ref||(domRefs[elementId].ref=document.getElementById(elementId)),domRefs[elementId].ref):(domRefs[elementId]={count:1,ref:null},document.getElementById(elementId)),createElement(tagName,options){const{className:className,textContent:textContent,innerHTML:innerHTML,attributes:attributes={}}=options,elem=document.createElement(tagName);for(let attribute in attributes)elem.setAttribute(attribute,attributes[attribute]);return className&&(elem.className=className),textContent&&(elem.textContent=textContent),innerHTML&&(elem.innerHTML=innerHTML),elem},debounce:(callback,wait)=>{let timeoutId=null;return(...args)=>{window.clearTimeout(timeoutId),timeoutId=window.setTimeout((()=>{callback.apply(null,args)}),wait)}},throttle(func,delay){timerId||(timerId=setTimeout((function(){func(),timerId=void 0}),delay))},delegate(el,event,selector,fn){el.addEventListener(event,(function(e){const potentialTarget=e.target.closest(selector);potentialTarget&&(e.delegateTarget=potentialTarget,fn.call(this,e))}))},formatTime(timestamp,format){try{String(timestamp).length<13&&(timestamp*=1e3);let[day,month,date,year]=new Date(timestamp).toString().split(" "),minutes=new Date(timestamp).getMinutes(),hours=new Date(timestamp).getHours();(new Date).toString().split(" ");minutes=minutes<10?`0${minutes}`:minutes;let finalHours="";switch(finalHours=hours>12?`${hours-12}:${minutes}`:0===hours?`12:${minutes}`:`${hours}:${minutes}`,finalHours=hours>=12?`${finalHours} PM`:`${finalHours} AM`,format){case"date-only":return`${month} ${date}, ${year}`;case"time-only":return finalHours;case"relative":return Date.now()-new Date(timestamp)<864e5?`${finalHours}`:relativeTime.from(timestamp);default:return`${month} ${date}, ${year} at ${finalHours}`}}catch(e){return console.error(e),timestamp}},formatAmount:(amount=0,currency="inr")=>amount?amount.toLocaleString(void 0,{currency:currency,maximumFractionDigits:8,minimumFractionDigits:0}):"0"};uiGlobals.connectionErrorNotification=[],navigator.onLine||uiGlobals.connectionErrorNotification.push(notify("There seems to be a problem connecting to the internet, Please check you internet connection.","error")),window.addEventListener("offline",(()=>{uiGlobals.connectionErrorNotification.push(notify("There seems to be a problem connecting to the internet, Please check you internet connection.","error"))})),window.addEventListener("online",(()=>{uiGlobals.connectionErrorNotification.forEach((notification=>notification.remove())),notify("We are back online.","success"),location.reload(),uiGlobals.connectionErrorNotification=[]}));let zIndex=50;function openPopup(popupId,pinned){return void 0===popupStack.peek()&&document.addEventListener("keydown",(e=>{"Escape"===e.key&&closePopup()})),zIndex++,getRef(popupId).setAttribute("style",`z-index: ${zIndex}`),getRef(popupId).show({pinned:pinned}),getRef(popupId)}function closePopup(options={}){void 0!==popupStack.peek()&&popupStack.peek().popup.hide(options)}document.addEventListener("popupopened",(async e=>{history.pushState({type:"popup"},null,null),e.target.id})),document.addEventListener("popupclosed",(e=>{zIndex--,e.target.id,void 0===popupStack.peek()&&document.removeEventListener("keydown",(e=>{"Escape"===e.key&&closePopup()}))})),window.addEventListener("popstate",(e=>{if(e.state&&"popup"===e.state.type)closePopup()}));const getConfirmation=(title,options={})=>new Promise((resolve=>{const{message:message="",cancelText:cancelText="Cancel",confirmText:confirmText="OK",danger:danger=!1}=options;getRef("confirm_title").innerText=title,getRef("confirm_message").innerText=message;const cancelButton=getRef("confirmation_popup").querySelector(".cancel-button"),confirmButton=getRef("confirmation_popup").querySelector(".confirm-button");confirmButton.textContent=confirmText,cancelButton.textContent=cancelText,danger?confirmButton.classList.add("button--danger"):confirmButton.classList.remove("button--danger");const{opened:opened,closed:closed}=openPopup("confirmation_popup");confirmButton.onclick=()=>{closePopup({payload:!0})},cancelButton.onclick=()=>{closePopup()},closed.then((payload=>{confirmButton.onclick=null,cancelButton.onclick=null,resolve(!!payload)}))}));function getPromptInput(title,message="",options={}){let{placeholder:placeholder="",isPassword:isPassword=!1,cancelText:cancelText="Cancel",confirmText:confirmText="OK"}=options;getRef("prompt_title").innerText=title,getRef("prompt_message").innerText=message;const cancelButton=getRef("prompt_popup").querySelector(".cancel-button"),confirmButton=getRef("prompt_popup").querySelector(".confirm-button");return isPassword&&(placeholder="Password",getRef("prompt_input").setAttribute("type","password")),getRef("prompt_input").setAttribute("placeholder",placeholder),getRef("prompt_input").focusIn(),cancelButton.textContent=cancelText,confirmButton.textContent=confirmText,openPopup("prompt_popup",!0),new Promise(((resolve,reject)=>{cancelButton.addEventListener("click",(()=>(closePopup(),null)),{once:!0}),confirmButton.addEventListener("click",(()=>{closePopup(),resolve(getRef("prompt_input").value)}),{once:!0})}))}function notify(message,mode,options={}){let icon;switch(mode){case"success":icon='';break;case"error":icon='',options.pinned=!0}return"error"===mode&&console.error(message),getRef("notification_drawer").push(message,{icon:icon,...options})}function detectBrowser(){let tem,ua=navigator.userAgent,M=ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i)||[];return/trident/i.test(M[1])?(tem=/\brv[ :]+(\d+)/g.exec(ua)||[],"IE "+(tem[1]||"")):"Chrome"===M[1]&&(tem=ua.match(/\b(OPR|Edge)\/(\d+)/),null!=tem)?tem.slice(1).join(" ").replace("OPR","Opera"):(M=M[2]?[M[1],M[2]]:[navigator.appName,navigator.appVersion,"-?"],null!=(tem=ua.match(/version\/(\d+)/i))&&M.splice(1,1,tem[1]),M.join(" "))}function createRipple(event,target){const circle=document.createElement("span"),diameter=Math.max(target.clientWidth,target.clientHeight),radius=diameter/2,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:1e3,fill:"forwards",easing:"ease-out"});target.append(circle),rippleAnimation.onfinish=()=>{circle.remove()}}window.addEventListener("hashchange",(e=>routeTo(window.location.hash))),window.addEventListener("load",(()=>{const[browserName,browserVersion]=detectBrowser().split(" "),supportedVersions={Chrome:85,Firefox:75,Safari:13};browserName in supportedVersions?parseInt(browserVersion){"Escape"===e.key&&closePopup()})),document.addEventListener("copy",(()=>{notify("copied","success")})),document.addEventListener("pointerdown",(e=>{e.target.closest("button:not([disabled]), .interactive")&&createRipple(e,e.target.closest("button, .interactive"))})),document.querySelectorAll(".popup__header__close, .close-popup-on-click").forEach((elem=>{elem.addEventListener("click",(()=>{closePopup()}))}))}));class Router{constructor(options={}){const{routes:routes={},state:state={},routingStart:routingStart,routingEnd:routingEnd}=options;this.routes=routes,this.state=state,this.routingStart=routingStart,this.routingEnd=routingEnd,this.lastPage=null,window.addEventListener("hashchange",(e=>this.routeTo(window.location.hash)))}addRoute(route,callback){this.routes[route]=callback}async routeTo(path){try{let page,queryString,params,wildcards=[];if([path,queryString]=path.split("?"),path.includes("#")&&(path=path.split("#")[1]),path.includes("/")?[,page,...wildcards]=path.split("/"):page=path,this.state={page:page,wildcards:wildcards,lastPage:this.lastPage},queryString&&(params=new URLSearchParams(queryString),this.state.params=Object.fromEntries(params)),this.routingStart&&this.routingStart(this.state),this.routes[page]){if(!document.startViewTransition)return await this.routes[page](this.state),void(this.lastPage=page);document.startViewTransition((async()=>{await this.routes[page](this.state),this.lastPage=page}))}else this.routes[404]?this.routes[404](this.state):console.error(`No route found for '${page}' and no '404' route is defined.`);this.routingEnd&&this.routingEnd(this.state)}catch(e){console.error(e)}}}const appState={params:{},openedPages:new Set},generalPages=["sign_up","sign_in","loading","landing"];async function routeTo(targetPage,options={}){const{firstLoad:firstLoad}=options;let pageId,subPageId1,subPageId2,searchParams,params;if(""===targetPage)try{floDapps.user.id&&(pageId="chat_page")}catch(e){pageId="landing"}else if(targetPage.includes("/")){let path;[path,searchParams]=targetPage.split("?"),[,pageId,subPageId1,subPageId2]=path.split("/")}else pageId=targetPage;if(document.querySelector(`#${pageId}`)?.classList.contains("inner-page")){try{floDapps.user.id&&generalPages.includes(pageId)&&(history.replaceState(null,null,"#/chat_page"),pageId="chat_page")}catch(e){if(!generalPages.includes(pageId))return}if(appState.currentPage=pageId,searchParams){const urlSearchParams=new URLSearchParams("?"+searchParams);params=Object.fromEntries(urlSearchParams.entries())}switch(params&&(appState.params=params),pageId){case"sign_in":setTimeout((()=>{getRef("private_key_field").focusIn()}),0);break;case"sign_up":getRef("keys_generator").generateKeys()}if(appState.lastPage!==pageId){document.querySelectorAll(".page").forEach((page=>page.classList.add("hidden"))),getRef(pageId).closest(".page").classList.remove("hidden"),document.querySelectorAll(".inner-page").forEach((page=>page.classList.add("hidden"))),getRef(pageId).classList.remove("hidden"),getRef(pageId).animate([{opacity:0,transform:"translateY(1rem)"},{opacity:1,transform:"translateY(0)"}],{duration:300,easing:"cubic-bezier(0.175, 0.885, 0.32, 1.275)"}).onfinish=()=>{},appState.lastPage=pageId}params&&(appState.params=params),appState.openedPages.add(pageId)}}class LazyLoader{constructor(container,elementsToRender,renderFn,options={}){const{batchSize:batchSize=10,freshRender:freshRender,bottomFirst:bottomFirst=!1,onEnd:onEnd}=options;this.elementsToRender=elementsToRender,this.arrayOfElements="function"==typeof elementsToRender?this.elementsToRender():elementsToRender||[],this.renderFn=renderFn,this.intersectionObserver,this.batchSize=batchSize,this.freshRender=freshRender,this.bottomFirst=bottomFirst,this.onEnd=onEnd,this.shouldLazyLoad=!1,this.lastScrollTop=0,this.lastScrollHeight=0,this.lazyContainer=document.querySelector(container),this.update=this.update.bind(this),this.render=this.render.bind(this),this.init=this.init.bind(this),this.clear=this.clear.bind(this)}get elements(){return this.arrayOfElements}init(){this.mutationObserver&&this.mutationObserver.disconnect(),this.intersectionObserver&&this.intersectionObserver.disconnect(),this.intersectionObserver=new IntersectionObserver(((entries,observer)=>{entries.forEach((entry=>{entry.isIntersecting&&(observer.disconnect(),this.render({lazyLoad:!0}))}))}),{root:this.lazyContainer}),this.mutationObserver=new MutationObserver((mutationList=>{mutationList.forEach((mutation=>{"childList"===mutation.type&&mutation.addedNodes.length&&(this.bottomFirst?this.lazyContainer.firstElementChild&&this.intersectionObserver.observe(this.lazyContainer.firstElementChild):this.lazyContainer.lastElementChild&&this.intersectionObserver.observe(this.lazyContainer.lastElementChild))}))})),this.mutationObserver.observe(this.lazyContainer,{childList:!0}),this.render()}update(elementsToRender){this.arrayOfElements="function"==typeof elementsToRender?this.elementsToRender():elementsToRender||[]}render(options={}){let{lazyLoad:lazyLoad=!1}=options;this.shouldLazyLoad=lazyLoad;const frag=document.createDocumentFragment();lazyLoad?this.bottomFirst?(this.updateEndIndex=this.updateStartIndex,this.updateStartIndex=this.updateEndIndex-this.batchSize):(this.updateStartIndex=this.updateEndIndex,this.updateEndIndex=this.updateEndIndex+this.batchSize):(this.intersectionObserver.disconnect(),this.bottomFirst?(this.updateEndIndex=this.arrayOfElements.length,this.updateStartIndex=this.updateEndIndex-this.batchSize-1):(this.updateStartIndex=0,this.updateEndIndex=this.batchSize),this.lazyContainer.innerHTML=""),this.lastScrollHeight=this.lazyContainer.scrollHeight,this.lastScrollTop=this.lazyContainer.scrollTop,this.arrayOfElements.slice(this.updateStartIndex,this.updateEndIndex).forEach(((element,index)=>{frag.append(this.renderFn(element))})),this.bottomFirst?(this.lazyContainer.prepend(frag),this.lastScrollTop+=this.lazyContainer.scrollHeight-this.lastScrollHeight,this.lazyContainer.scrollTo({top:this.lastScrollTop}),this.lastScrollHeight=this.lazyContainer.scrollHeight,this.updateStartIndex<=0&&this.onEnd&&(this.mutationObserver.disconnect(),this.intersectionObserver.disconnect(),this.onEnd())):(this.lazyContainer.append(frag),this.updateEndIndex>=this.arrayOfElements.length&&this.onEnd&&(this.mutationObserver.disconnect(),this.intersectionObserver.disconnect(),this.onEnd())),!lazyLoad&&this.bottomFirst&&(this.lazyContainer.scrollTop=this.lazyContainer.scrollHeight),!lazyLoad&&this.freshRender&&this.freshRender()}clear(){this.intersectionObserver.disconnect(),this.mutationObserver.disconnect(),this.lazyContainer.innerHTML=""}reset(){this.arrayOfElements="function"==typeof this.elementsToRender?this.elementsToRender():this.elementsToRender||[],this.render()}}const slideInLeft=[{opacity:0,transform:"translateX(1.5rem)"},{opacity:1,transform:"translateX(0)"}],slideOutLeft=[{opacity:1,transform:"translateX(0)"},{opacity:0,transform:"translateX(-1.5rem)"}],slideInRight=[{opacity:0,transform:"translateX(-1.5rem)"},{opacity:1,transform:"translateX(0)"}],slideOutRight=[{opacity:1,transform:"translateX(0)"},{opacity:0,transform:"translateX(1.5rem)"}],slideInDown=[{opacity:0,transform:"translateY(-1.5rem)"},{opacity:1,transform:"translateY(0)"}],slideOutUp=[{opacity:1,transform:"translateY(0)"},{opacity:0,transform:"translateY(-1.5rem)"}];function showChildElement(id,index,options={}){return new Promise((resolve=>{const{mobileView:mobileView=!1,entry:entry,exit:exit}=options,animOptions={duration:150,easing:"ease",fill:"forwards"},parent="string"==typeof id?document.getElementById(id):id,visibleElement=[...parent.children].find((elem=>!elem.classList.contains(mobileView?"hide-on-mobile":"hidden")));visibleElement!==parent.children[index]&&(visibleElement.getAnimations().forEach((anim=>anim.cancel())),parent.children[index].getAnimations().forEach((anim=>anim.cancel())),visibleElement?exit?visibleElement.animate(exit,animOptions).onfinish=()=>{visibleElement.classList.add(mobileView?"hide-on-mobile":"hidden"),parent.children[index].classList.remove(mobileView?"hide-on-mobile":"hidden"),entry&&(parent.children[index].animate(entry,animOptions).onfinish=()=>resolve())}:(visibleElement.classList.add(mobileView?"hide-on-mobile":"hidden"),parent.children[index].classList.remove(mobileView?"hide-on-mobile":"hidden"),resolve()):(parent.children[index].classList.remove(mobileView?"hide-on-mobile":"hidden"),parent.children[index].animate(entry,animOptions).onfinish=()=>resolve()))}))}function buttonLoader(id,show){const button="string"==typeof id?document.getElementById(id):id;button.disabled=show;const animOptions={duration:200,fill:"forwards",easing:"ease"};if(show)button.parentNode.append(createElement("sm-spinner")),button.animate([{clipPath:"circle(100%)"},{clipPath:"circle(0)"}],animOptions);else{button.getAnimations().forEach((anim=>anim.cancel()));const potentialTarget=button.parentNode.querySelector("sm-spinner");potentialTarget&&potentialTarget.remove()}}let currentSubscriber=null;function $signal(initialValue){let value=initialValue;const subscribers=new Set;return[function(){if(currentSubscriber){const weakRef=new WeakRef({func:currentSubscriber});subscribers.add(weakRef)}return value},function(newValue){if(newValue!==value){value=newValue;for(const subscriber of subscribers){const ref=subscriber.deref();ref&&ref.func()}}}]}async function $effect(fn){currentSubscriber=fn;const result=fn();try{result instanceof Promise&&await result}catch(e){console.error(e)}finally{currentSubscriber=null}} \ No newline at end of file