3056 lines
120 KiB
HTML
3056 lines
120 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Bob's Fund on Blockchain</title>
|
|
<link rel="stylesheet" href="css/main.min.css">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com">
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400..900&display=swap" rel="stylesheet">
|
|
<script id="floGlobals">
|
|
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
|
|
const floGlobals = {
|
|
blockchain: "FLO",
|
|
adminID: "FFXy5pJnfzu2fCDLhpUremyXQjGtFpgCDN",
|
|
application: "BobsFund"
|
|
}
|
|
</script>
|
|
<script src="scripts/lib.js"></script>
|
|
<script src="scripts/floCrypto.js"></script>
|
|
<script src="scripts/floBlockchainAPI.js"></script>
|
|
<script src="scripts/floExchangeAPI.js"></script>
|
|
<script src="scripts/compactIDB.js"></script>
|
|
<script src="scripts/bobs-fund.js"></script>
|
|
<script>
|
|
function onLoadStartUp() {
|
|
floExchangeAPI.init().then(result => {
|
|
compactIDB.initDB(floGlobals.application, {
|
|
funds: {},
|
|
appendix: {}
|
|
}).then(result => refresh())
|
|
.catch(error => console.error(error))
|
|
}).catch(error => reject(error))
|
|
}
|
|
</script>
|
|
</head>
|
|
|
|
<body onload="onLoadStartUp()" data-theme="light">
|
|
<sm-notifications id="notification_drawer"></sm-notifications>
|
|
<article id="loading_page" class="page">
|
|
<h2 class="h2">Bob's Fund</h2>
|
|
<svg id="loader" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><polygon points="17.76 23.78 17.76 40.22 32 48.44 46.24 40.22 46.24 23.78 32 15.56 17.76 23.78"/><polyline points="17.76 23.78 32 32 46.24 23.78"/><line x1="32" y1="48.44" x2="32" y2="32"/><polyline points="32 2 6.02 17 6.02 47"/><polyline points="57.98 47 57.98 17 32 2"/><polyline points="6.02 47 32 62 57.98 47"/></svg>
|
|
<h4>Loading data from blockchain</h4>
|
|
<footer class="page__footer flex align-center">
|
|
<svg id="rm_logo" class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20.46,21.32C20,19.78,18.6,18.59,15.3,17a12.67,12.67,0,0,1-2.64-1.56,4.27,4.27,0,0,1-.79-1,2.6,2.6,0,0,1,0-1.41c.24-.68.49-1,2.43-2.85a7.18,7.18,0,0,0,2.09-2.92,4.25,4.25,0,0,0,0-1.77,6.52,6.52,0,0,0-2.85-3.11c-.56-.36-.81-.4-.81-.15a2.33,2.33,0,0,1-.18.45L12.4,3l-.53-.36c-.28-.21-.64-.41-.77-.49s-.46-.11-.46,0a6.21,6.21,0,0,1-.37.83s-.08,0-.17-.08c-1.15-.83-1.64-1-1.64-.73A7.33,7.33,0,0,1,7.7,3.65C6.48,5.68,5.24,6.7,4,6.7c-.56,0-.54,0-.37.64s.2.58.68.43a3.37,3.37,0,0,0,1.09-.54.86.86,0,0,1,.3-.17,1.34,1.34,0,0,1,.13.39.79.79,0,0,0,.17.4A3.5,3.5,0,0,0,7.37,7.3L7.8,7l.09.34c.12.45.19.51.62.39a4.25,4.25,0,0,0,2.17-1.54l.38-.45,0,.39A5.92,5.92,0,0,1,8.89,9.54L7.67,10.71c-2,1.93-1.89,3.51.37,5a27.41,27.41,0,0,0,2.89,1.51c.17.07.62.32,1,.54C14,19,15,20.23,15,21.48a2,2,0,0,0,0,.49h0c0,.05,0,.05.56-.1a1.89,1.89,0,0,0,.53-.21,2.41,2.41,0,0,0-.34-1.15,7.05,7.05,0,0,0-1.68-1.77,21.91,21.91,0,0,0-3.2-1.83A9.53,9.53,0,0,1,8.16,15.2a2.18,2.18,0,0,1-.74-1.55C7.42,12.79,7.86,12,9,11c1.77-1.64,2.45-2.45,2.92-3.55a2.28,2.28,0,0,0,.26-1.26A2,2,0,0,0,12,5.06l-.2-.45L12,4.3l.28-.49.09-.18L12.6,4a3.69,3.69,0,0,1,.61,1.76A3.47,3.47,0,0,1,12.94,7l-.09.25s-.21.37-.41.69A17.78,17.78,0,0,1,9.91,10.6c-1.07,1-1.43,1.62-1.47,2.47a2.05,2.05,0,0,0,.7,1.73,10.47,10.47,0,0,0,3.28,2.08c2.28,1.13,3.26,1.81,4,2.73a2.94,2.94,0,0,1,.74,1.75,1.26,1.26,0,0,0,.09.57.48.48,0,0,0,.26,0l.51-.13.29-.08,0-.28c-.13-1-1-2-2.47-3a25.52,25.52,0,0,0-3.26-1.77,8.59,8.59,0,0,1-2.23-1.43,2.09,2.09,0,0,1-.5-2.62c.26-.53.5-.83,2.35-2.6,1.51-1.45,2.15-2.58,2.15-3.79A3.67,3.67,0,0,0,13,3.48a3,3,0,0,1-.4-.42A1.85,1.85,0,0,1,13,2.33a6.74,6.74,0,0,1,1.83,1.73,2.62,2.62,0,0,1,.47,1.68,3,3,0,0,1-.55,1.84c-.45.78-.79,1.14-2.67,2.93a5.56,5.56,0,0,0-1.3,1.64,1.77,1.77,0,0,0-.21,1,1.76,1.76,0,0,0,.19.92,6.28,6.28,0,0,0,2.9,2.34,21.6,21.6,0,0,1,3.66,2c1.35,1,2,2,2,3a1.06,1.06,0,0,0,.05.47,2.83,2.83,0,0,0,1-.24C20.56,21.68,20.56,21.66,20.46,21.32ZM7.29,6.4h0a2.23,2.23,0,0,1-.9.28L6,6.72l.43-.53a15.22,15.22,0,0,0,1.89-3,3.52,3.52,0,0,1,.38-.67c.07-.08.49.2,1,.64l.39.35L9.66,4A6.7,6.7,0,0,1,7.29,6.4Zm3.58-1.11A5.8,5.8,0,0,1,9.25,6.51h0a3.3,3.3,0,0,1-.74.17l-.35,0,.39-.49a15.64,15.64,0,0,0,1.32-2,4.63,4.63,0,0,1,.28-.49c.06-.08.33.26.57.77l.28.57Zm1-1.4a1.63,1.63,0,0,1-.28.4A6.63,6.63,0,0,1,11,3.72l-.53-.56.12-.29c.2-.49.24-.51.64-.19a5.57,5.57,0,0,1,.85.78A2.78,2.78,0,0,1,11.87,3.89Z"/></svg>
|
|
<h4 class="h4 color-0-8 weight-500">RanchiMall</h4>
|
|
</footer>
|
|
</article>
|
|
<article id="error_page" class="page hide-completely">
|
|
<h2 class="h2">Bob's Fund</h2>
|
|
<svg class="sad-face" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
|
<g class="face">
|
|
<g class="eyes">
|
|
<circle cx="187.52" cy="249.17" r="13.42"/>
|
|
<circle cx="312.48" cy="249.17" r="13.42"/>
|
|
</g>
|
|
<path d="M288,349.35a6.07,6.07,0,0,1-3.81-1.34c-.09-.07-12.31-9.4-34.14-9.4S216,347.94,215.83,348a6.1,6.1,0,0,1-7.59-9.55c.61-.5,15.4-12.08,41.76-12.08s41.15,11.58,41.76,12.08A6.1,6.1,0,0,1,288,349.35Z"/>
|
|
<path d="M344.21,237.41a52,52,0,0,1-23.45-6c-15.91-8.09-21.28-19.61-21.5-20.1a4.07,4.07,0,0,1,7.41-3.36c0,.08,4.56,9.49,17.77,16.21S347.93,229,348,229a4.07,4.07,0,0,1,1.63,8A29.26,29.26,0,0,1,344.21,237.41Z"/>
|
|
<path d="M155.79,237.41a29.26,29.26,0,0,1-5.45-.43,4.07,4.07,0,0,1,1.63-8c.2,0,10.43,1.87,23.59-4.81s17.73-16.13,17.78-16.23a4.07,4.07,0,0,1,7.4,3.38c-.22.49-5.59,12-21.5,20.1A52,52,0,0,1,155.79,237.41Z"/>
|
|
</g>
|
|
<path d="M450.16,220.16A200.16,200.16,0,0,0,108.46,78.63,200.69,200.69,0,0,0,57.61,275.68V407.4a10.17,10.17,0,1,0,20.33,0V322.6a201.91,201.91,0,0,0,99.41,84.19v52.27l-11.45.62a10.17,10.17,0,0,0,.54,20.32H167l30.69-1.66V413.45a202.3,202.3,0,0,0,104.62,0v64.88L333,480h.56a10.17,10.17,0,0,0,.54-20.32l-11.45-.62V406.79a201.91,201.91,0,0,0,99.41-84.19v84.8a10.17,10.17,0,1,0,20.33,0V275.68A200.81,200.81,0,0,0,450.16,220.16ZM250,400c-99.16,0-179.83-80.67-179.83-179.83S150.84,40.34,250,40.34,429.83,121,429.83,220.16,349.16,400,250,400Z"/></svg>
|
|
<h4 class="margin-bottom-0-5r">Oops, Couldn't get data from blockchain</h4>
|
|
<footer class="page__footer flex align-center">
|
|
<svg id="rm_logo" class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20.46,21.32C20,19.78,18.6,18.59,15.3,17a12.67,12.67,0,0,1-2.64-1.56,4.27,4.27,0,0,1-.79-1,2.6,2.6,0,0,1,0-1.41c.24-.68.49-1,2.43-2.85a7.18,7.18,0,0,0,2.09-2.92,4.25,4.25,0,0,0,0-1.77,6.52,6.52,0,0,0-2.85-3.11c-.56-.36-.81-.4-.81-.15a2.33,2.33,0,0,1-.18.45L12.4,3l-.53-.36c-.28-.21-.64-.41-.77-.49s-.46-.11-.46,0a6.21,6.21,0,0,1-.37.83s-.08,0-.17-.08c-1.15-.83-1.64-1-1.64-.73A7.33,7.33,0,0,1,7.7,3.65C6.48,5.68,5.24,6.7,4,6.7c-.56,0-.54,0-.37.64s.2.58.68.43a3.37,3.37,0,0,0,1.09-.54.86.86,0,0,1,.3-.17,1.34,1.34,0,0,1,.13.39.79.79,0,0,0,.17.4A3.5,3.5,0,0,0,7.37,7.3L7.8,7l.09.34c.12.45.19.51.62.39a4.25,4.25,0,0,0,2.17-1.54l.38-.45,0,.39A5.92,5.92,0,0,1,8.89,9.54L7.67,10.71c-2,1.93-1.89,3.51.37,5a27.41,27.41,0,0,0,2.89,1.51c.17.07.62.32,1,.54C14,19,15,20.23,15,21.48a2,2,0,0,0,0,.49h0c0,.05,0,.05.56-.1a1.89,1.89,0,0,0,.53-.21,2.41,2.41,0,0,0-.34-1.15,7.05,7.05,0,0,0-1.68-1.77,21.91,21.91,0,0,0-3.2-1.83A9.53,9.53,0,0,1,8.16,15.2a2.18,2.18,0,0,1-.74-1.55C7.42,12.79,7.86,12,9,11c1.77-1.64,2.45-2.45,2.92-3.55a2.28,2.28,0,0,0,.26-1.26A2,2,0,0,0,12,5.06l-.2-.45L12,4.3l.28-.49.09-.18L12.6,4a3.69,3.69,0,0,1,.61,1.76A3.47,3.47,0,0,1,12.94,7l-.09.25s-.21.37-.41.69A17.78,17.78,0,0,1,9.91,10.6c-1.07,1-1.43,1.62-1.47,2.47a2.05,2.05,0,0,0,.7,1.73,10.47,10.47,0,0,0,3.28,2.08c2.28,1.13,3.26,1.81,4,2.73a2.94,2.94,0,0,1,.74,1.75,1.26,1.26,0,0,0,.09.57.48.48,0,0,0,.26,0l.51-.13.29-.08,0-.28c-.13-1-1-2-2.47-3a25.52,25.52,0,0,0-3.26-1.77,8.59,8.59,0,0,1-2.23-1.43,2.09,2.09,0,0,1-.5-2.62c.26-.53.5-.83,2.35-2.6,1.51-1.45,2.15-2.58,2.15-3.79A3.67,3.67,0,0,0,13,3.48a3,3,0,0,1-.4-.42A1.85,1.85,0,0,1,13,2.33a6.74,6.74,0,0,1,1.83,1.73,2.62,2.62,0,0,1,.47,1.68,3,3,0,0,1-.55,1.84c-.45.78-.79,1.14-2.67,2.93a5.56,5.56,0,0,0-1.3,1.64,1.77,1.77,0,0,0-.21,1,1.76,1.76,0,0,0,.19.92,6.28,6.28,0,0,0,2.9,2.34,21.6,21.6,0,0,1,3.66,2c1.35,1,2,2,2,3a1.06,1.06,0,0,0,.05.47,2.83,2.83,0,0,0,1-.24C20.56,21.68,20.56,21.66,20.46,21.32ZM7.29,6.4h0a2.23,2.23,0,0,1-.9.28L6,6.72l.43-.53a15.22,15.22,0,0,0,1.89-3,3.52,3.52,0,0,1,.38-.67c.07-.08.49.2,1,.64l.39.35L9.66,4A6.7,6.7,0,0,1,7.29,6.4Zm3.58-1.11A5.8,5.8,0,0,1,9.25,6.51h0a3.3,3.3,0,0,1-.74.17l-.35,0,.39-.49a15.64,15.64,0,0,0,1.32-2,4.63,4.63,0,0,1,.28-.49c.06-.08.33.26.57.77l.28.57Zm1-1.4a1.63,1.63,0,0,1-.28.4A6.63,6.63,0,0,1,11,3.72l-.53-.56.12-.29c.2-.49.24-.51.64-.19a5.57,5.57,0,0,1,.85.78A2.78,2.78,0,0,1,11.87,3.89Z"/></svg>
|
|
<h4 class="h4 color-0-8 weight-500">RanchiMall</h4>
|
|
</footer>
|
|
</article>
|
|
<header id="main_header" class="hide-completely">
|
|
<div class="flex align-center">
|
|
<svg id="main_header__logo" class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20.46,21.32C20,19.78,18.6,18.59,15.3,17a12.67,12.67,0,0,1-2.64-1.56,4.27,4.27,0,0,1-.79-1,2.6,2.6,0,0,1,0-1.41c.24-.68.49-1,2.43-2.85a7.18,7.18,0,0,0,2.09-2.92,4.25,4.25,0,0,0,0-1.77,6.52,6.52,0,0,0-2.85-3.11c-.56-.36-.81-.4-.81-.15a2.33,2.33,0,0,1-.18.45L12.4,3l-.53-.36c-.28-.21-.64-.41-.77-.49s-.46-.11-.46,0a6.21,6.21,0,0,1-.37.83s-.08,0-.17-.08c-1.15-.83-1.64-1-1.64-.73A7.33,7.33,0,0,1,7.7,3.65C6.48,5.68,5.24,6.7,4,6.7c-.56,0-.54,0-.37.64s.2.58.68.43a3.37,3.37,0,0,0,1.09-.54.86.86,0,0,1,.3-.17,1.34,1.34,0,0,1,.13.39.79.79,0,0,0,.17.4A3.5,3.5,0,0,0,7.37,7.3L7.8,7l.09.34c.12.45.19.51.62.39a4.25,4.25,0,0,0,2.17-1.54l.38-.45,0,.39A5.92,5.92,0,0,1,8.89,9.54L7.67,10.71c-2,1.93-1.89,3.51.37,5a27.41,27.41,0,0,0,2.89,1.51c.17.07.62.32,1,.54C14,19,15,20.23,15,21.48a2,2,0,0,0,0,.49h0c0,.05,0,.05.56-.1a1.89,1.89,0,0,0,.53-.21,2.41,2.41,0,0,0-.34-1.15,7.05,7.05,0,0,0-1.68-1.77,21.91,21.91,0,0,0-3.2-1.83A9.53,9.53,0,0,1,8.16,15.2a2.18,2.18,0,0,1-.74-1.55C7.42,12.79,7.86,12,9,11c1.77-1.64,2.45-2.45,2.92-3.55a2.28,2.28,0,0,0,.26-1.26A2,2,0,0,0,12,5.06l-.2-.45L12,4.3l.28-.49.09-.18L12.6,4a3.69,3.69,0,0,1,.61,1.76A3.47,3.47,0,0,1,12.94,7l-.09.25s-.21.37-.41.69A17.78,17.78,0,0,1,9.91,10.6c-1.07,1-1.43,1.62-1.47,2.47a2.05,2.05,0,0,0,.7,1.73,10.47,10.47,0,0,0,3.28,2.08c2.28,1.13,3.26,1.81,4,2.73a2.94,2.94,0,0,1,.74,1.75,1.26,1.26,0,0,0,.09.57.48.48,0,0,0,.26,0l.51-.13.29-.08,0-.28c-.13-1-1-2-2.47-3a25.52,25.52,0,0,0-3.26-1.77,8.59,8.59,0,0,1-2.23-1.43,2.09,2.09,0,0,1-.5-2.62c.26-.53.5-.83,2.35-2.6,1.51-1.45,2.15-2.58,2.15-3.79A3.67,3.67,0,0,0,13,3.48a3,3,0,0,1-.4-.42A1.85,1.85,0,0,1,13,2.33a6.74,6.74,0,0,1,1.83,1.73,2.62,2.62,0,0,1,.47,1.68,3,3,0,0,1-.55,1.84c-.45.78-.79,1.14-2.67,2.93a5.56,5.56,0,0,0-1.3,1.64,1.77,1.77,0,0,0-.21,1,1.76,1.76,0,0,0,.19.92,6.28,6.28,0,0,0,2.9,2.34,21.6,21.6,0,0,1,3.66,2c1.35,1,2,2,2,3a1.06,1.06,0,0,0,.05.47,2.83,2.83,0,0,0,1-.24C20.56,21.68,20.56,21.66,20.46,21.32ZM7.29,6.4h0a2.23,2.23,0,0,1-.9.28L6,6.72l.43-.53a15.22,15.22,0,0,0,1.89-3,3.52,3.52,0,0,1,.38-.67c.07-.08.49.2,1,.64l.39.35L9.66,4A6.7,6.7,0,0,1,7.29,6.4Zm3.58-1.11A5.8,5.8,0,0,1,9.25,6.51h0a3.3,3.3,0,0,1-.74.17l-.35,0,.39-.49a15.64,15.64,0,0,0,1.32-2,4.63,4.63,0,0,1,.28-.49c.06-.08.33.26.57.77l.28.57Zm1-1.4a1.63,1.63,0,0,1-.28.4A6.63,6.63,0,0,1,11,3.72l-.53-.56.12-.29c.2-.49.24-.51.64-.19a5.57,5.57,0,0,1,.85.78A2.78,2.78,0,0,1,11.87,3.89Z"/></svg>
|
|
<div class="grid">
|
|
<h5 class="header__company-name">RanchiMall</h5>
|
|
<h3 class="header__app-name">Bob's Fund</h3>
|
|
</div>
|
|
</div>
|
|
<div id="current_price" class="grid gap-1 flow-column align-center">
|
|
<span id="btc-usd-rate" class="weight-700"></span>
|
|
<span id="usd-rate" class="weight-700"></span>
|
|
</div>
|
|
<div class="dropdown">
|
|
<button id="profile_button" onclick="changeDropdownState('profile_dropdown', 'toggle', this)">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11.93,0a4.8,4.8,0,0,0-4.8,4.8s.13,3.74.54,4.53c.5,1,3,3,4.26,3s3.72-2,4.22-3c.41-.79.58-4.53.58-4.53A4.8,4.8,0,0,0,11.93,0Z"/><path d="M14.47,12.59a4.38,4.38,0,0,1-2.54,1,4.38,4.38,0,0,1-2.53-1c-1,1.78-4,2.69-6.21,3.68-.91.37-1.67,5.3-.61,5.3A18.4,18.4,0,0,0,12.12,24a16.76,16.76,0,0,0,9.17-2.42c1.06,0,.3-4.89-.61-5.3C18.45,15.28,15.42,14.37,14.47,12.59Z"/></svg>
|
|
</button>
|
|
<ul id="profile_dropdown" class="dropdown__panel hide-completely">
|
|
<li class="grid gap-0-5">
|
|
<h4 class="weight-700 margin-bottom-0-5r">Preferred currency</h4>
|
|
<p>This will convert all amounts to preferred currency.</p>
|
|
<sm-select id="currency_selector" align-select="right" class="justify-self-start">
|
|
<sm-option value="inr">INR</sm-option>
|
|
<sm-option value="usd">USD</sm-option>
|
|
</sm-select>
|
|
</li>
|
|
<li>
|
|
<sm-switch id="theme_switcher">
|
|
<div slot="left" class="flex weight-700">Dark theme</div>
|
|
</sm-switch>
|
|
</li>
|
|
<li class="interact weight-700" onclick="showPage('admin_page')">
|
|
Admin panel
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</header>
|
|
<main id="home_page" class="page page-layout hide-completely">
|
|
<section id="homepage__hero-section" class="full-bleed page-layout">
|
|
<h2 class="h2 weight-700 margin-bottom-1r">Bob's Fund<br>on FLO Blockchain</h2>
|
|
<p>
|
|
Bob's Fund is a 20 year long term Bitcoin price linked product. Investors are entitled to 100%
|
|
of Bitcoin price gains, but they must hold for 20 years. Over a very long time period, investor returns
|
|
on an asset like Bitcoin should outstrip returns on conventional assets like real estate and stocks. The
|
|
management fees on this product is zero. RanchiMall earns by having invested an equal amount as
|
|
every investor, thus the interests of fund manager, and fund investors are totally aligned.
|
|
</p>
|
|
</section>
|
|
<header class="fund-list__header grid margin-bottom-1-5r">
|
|
<h3 class="h3">Funds</h3>
|
|
<sm-input id="search_investor" type="search" placeholder="Search investor with FLO ID">
|
|
<svg slot="icon" class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.031 16.617l4.283 4.282-1.415 1.415-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9 9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617zm-2.006-.742A6.977 6.977 0 0 0 18 11c0-3.868-3.133-7-7-7-3.868 0-7 3.132-7 7 0 3.867 3.132 7 7 7a6.977 6.977 0 0 0 4.875-1.975l.15-.15z"/></svg>
|
|
</sm-input>
|
|
<button id="refresh_button" class="justify-right button--primary">Refresh</button>
|
|
</header>
|
|
<div class="warning-container grid gap-1">
|
|
<p>
|
|
When redemption is due, redeem button will appear next to the FLO ID. Enter your private key to redeem.
|
|
</p>
|
|
<strong style="color: #fb3640">Do not enter private key in similar looking pages.</strong>
|
|
</div>
|
|
<section id="fund_list"></ul>
|
|
<div id="fund_list__empty-state" class="grid hide-completely">
|
|
<svg class="icon icon--big" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm-4-6h8v2H8v-2zm0-3a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm8 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/></svg>
|
|
<h4>No Funds found</h4>
|
|
</div>
|
|
</main>
|
|
<article id="admin_page" class="page page-layout hide-completely">
|
|
<header class="flex margin-top-1-5 align-center margin-bottom-1-5r">
|
|
<button onclick="showPage('home_page')">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M7.828 11H20v2H7.828l5.364 5.364-1.414 1.414L4 12l7.778-7.778 1.414 1.414z"/></svg>
|
|
</button>
|
|
<h3>Admin Panel</h3>
|
|
</header>
|
|
<form id="create_fund_form" class="grid gap-1-5">
|
|
<sm-switch id="fund_creation_toggle">
|
|
<div class="flex" slot="left">
|
|
Add investors to existing fund
|
|
</div>
|
|
</sm-switch>
|
|
<section id="fund_details_form" class="grid gap-1-5 margin-bottom-0-5r">
|
|
<label class="grid gap-0-5">
|
|
Fund start date
|
|
<input type="date" name="start_date" required>
|
|
</label>
|
|
<label class="grid gap-0-5">
|
|
Base BTC value ($)
|
|
<input type="number" name="btc_base" step="0.01" pattern="[\d,]+(.\d+)?" required>
|
|
</label>
|
|
<label class="grid gap-0-5">
|
|
Base USD rate (₹)
|
|
<input type="number" name="usd_rate" min=0 step="0.01" required>
|
|
</label>
|
|
<div class="grid gap-0-5">
|
|
Maximum Duration
|
|
<div class="flex">
|
|
<input type="number" name="max_pv" inputmode="numeric" required>
|
|
<sm-select id="max_pt" align-select="right">
|
|
<sm-option value="year(s)" selected>year(s)</sm-option>
|
|
<sm-option value="month(s)">month(s)</sm-option>
|
|
<sm-option value="week(s)">week(s)</sm-option>
|
|
<sm-option value="day(s)">day(s)</sm-option>
|
|
</sm-select>
|
|
</div>
|
|
</div>
|
|
<sm-switch id="tapout_toggle">
|
|
<div class="flex" slot="left">
|
|
Tapout
|
|
</div>
|
|
</sm-switch>
|
|
<section id="tapout_container" class="grid gap-1-5 hide-completely">
|
|
<div class="grid gap-0-5">
|
|
Tapout window
|
|
<div class="flex">
|
|
<input type="number" name="tap_wv" inputmode="numeric" disabled required>
|
|
<sm-select id="tap_wt" align-select="right">
|
|
<sm-option value="year(s)">year(s)</sm-option>
|
|
<sm-option value="month(s)" selected>month(s)</sm-option>
|
|
<sm-option value="week(s)">week(s)</sm-option>
|
|
<sm-option value="day(s)">day(s)</sm-option>
|
|
</sm-select>
|
|
</div>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
Tapout interval(Use comma serated values. e.g. 10, 15...)
|
|
<div class="flex">
|
|
<input type="text" name="tap_iv" disabled required>
|
|
<sm-select id="tap_it" align-select="right">
|
|
<sm-option value="year(s)" selected>year(s)</sm-option>
|
|
<sm-option value="month(s)">month(s)</sm-option>
|
|
</sm-select>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<label class="grid gap-0-5">
|
|
Fee
|
|
<input type="number" name="fee" min=0 max=100 step="0.01" value="0" inputmode="numeric" required>
|
|
</label>
|
|
</section>
|
|
|
|
<div id="fund_selector_container" class="grid margin-bottom-0-5r hide-completely">
|
|
<span class="margin-bottom-0-5r">Select Fund</span>
|
|
<sm-select id="fund_selector"></sm-select>
|
|
</div>
|
|
<h4>Add Investors</h4>
|
|
<ul id="investors_input_list" class="grid gap-1">
|
|
<li class="investor-input grid">
|
|
<sm-input placeholder="FLO ID" class="outlined" animate></sm-input>
|
|
<sm-input placeholder="Amount(₹)" type="number" min=0 class="outlined" animate></sm-input>
|
|
<button class="remove-investor" title="Remove this investor">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/></svg>
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
<button type="button" class="margin-bottom-1r justify-self-start" style="margin-top: -1rem;" onclick="renderInvestor()">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/></svg>
|
|
Add new
|
|
</button>
|
|
<button id="add_investors_button" type="submit" class="button--primary justify-self-start hide-completely">Add Investors</button>
|
|
<button id="create_fund_button" type="submit" class="button--primary justify-self-start">Create Fund</button>
|
|
</form>
|
|
</article>
|
|
<article id="confirm_term_page" class="page page-layout hide-completely">
|
|
<header class="flex margin-top-1-5 align-center margin-bottom-1-5r">
|
|
<button class="back-button" onclick="showPage('admin_page')">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M7.828 11H20v2H7.828l5.364 5.364-1.414 1.414L4 12l7.778-7.778 1.414 1.414z"/></svg>
|
|
</button>
|
|
<h3>Confirm Details</h3>
|
|
</header>
|
|
<form class="grid gap-1-5" onsubmit="return false">
|
|
<section id="term_details" class="breakable"></section>
|
|
<div class="grid">
|
|
<h4 class="weight-400 margin-bottom-1r" id="term_admin_id"></h4>
|
|
<sm-input id="get_term_private_key" type="password" placeholder="Private key"></sm-input>
|
|
</div>
|
|
<button id="create_term_button" type="submit" class="button--primary justify-self-start">Create term</button>
|
|
</form>
|
|
</article>
|
|
<article id="confirm_fund_page" class="page page-layout hide-completely">
|
|
<header class="flex margin-top-1-5 align-center margin-bottom-1-5r">
|
|
<button class="back-button" onclick="showPage('admin_page')">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M7.828 11H20v2H7.828l5.364 5.364-1.414 1.414L4 12l7.778-7.778 1.414 1.414z"/></svg>
|
|
</button>
|
|
<h3>Confirm Details</h3>
|
|
</header>
|
|
<form class="grid gap-1-5" onsubmit="return false">
|
|
<section id="fund_details" class="breakable"></section>
|
|
<div class="grid">
|
|
<h4 class="weight-400 margin-bottom-1r" id="fund_admin_id"></h4>
|
|
<sm-input id="get_fund_private_key" type="password" placeholder="Private key"></sm-input>
|
|
</div>
|
|
<button id="confirm_fund_button" type="submit" class="button--primary justify-self-start">Create Fund</button>
|
|
</form>
|
|
</article>
|
|
|
|
|
|
<script id="ui">
|
|
const fundInvestorTemplate = document.createElement('template')
|
|
fundInvestorTemplate.innerHTML = `
|
|
<style>
|
|
.flex{
|
|
display: flex;
|
|
}
|
|
.grid{
|
|
display: grid;
|
|
grid-auto-columns: minmax(0, 1fr);
|
|
}
|
|
.gap-1{
|
|
gap: 1rem;
|
|
}
|
|
.gap-1-5{
|
|
gap: 1.5rem;
|
|
}
|
|
.flow-column{
|
|
grid-auto-flow: column;
|
|
}
|
|
.justify-end{
|
|
justify-content: flex-end;
|
|
}
|
|
.hide-completely{
|
|
display: none !important;
|
|
}
|
|
.fund-investor{
|
|
display: grid;
|
|
gap: 1rem;
|
|
padding: 1rem 0;
|
|
background-color: rgba(var(--foreground-color), 1);
|
|
}
|
|
.transaction-column{
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 9rem;
|
|
}
|
|
.space-between{
|
|
justify-content: space-between;
|
|
}
|
|
.label{
|
|
font-weight: 500;
|
|
font-size: 0.75rem;
|
|
margin-bottom: 0.2rem;
|
|
color: rgba(var(--text-color), 0.8);
|
|
}
|
|
.value{
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
overflow-wrap: break-word;
|
|
word-wrap: break-word;
|
|
-ms-word-break: break-all;
|
|
word-break: break-word;
|
|
-ms-hyphens: auto;
|
|
-moz-hyphens: auto;
|
|
-webkit-hyphens: auto;
|
|
hyphens: auto;
|
|
}
|
|
@media only screen and (min-width: 640px) {
|
|
.justify-self-end{
|
|
justify-self: flex-end;
|
|
text-align: right;
|
|
}
|
|
}
|
|
@media only screen and (min-width: 1280px) {
|
|
.fund-investor{
|
|
gap: 1.5rem;
|
|
grid-template-columns: 1fr auto;
|
|
}
|
|
}
|
|
</style>
|
|
<li class="fund-investor">
|
|
<div class="grid">
|
|
<span class="label">FLO ID</span>
|
|
<span class="value flo-id"></span>
|
|
</div>
|
|
<div class="grid flow-column gap-1-5 space-between">
|
|
<div class="transaction-column">
|
|
<span class="label">Invested</span>
|
|
<span class="value amount-invested"></span>
|
|
</div>
|
|
<div class="transaction-column">
|
|
<span class="label">Present value</span>
|
|
<span class="value net-value" style="color: var(--green)"></span>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
`
|
|
customElements.define('fund-investor', class extends HTMLElement {
|
|
constructor() {
|
|
super()
|
|
this.shadow = this.attachShadow({
|
|
mode: 'open'
|
|
})
|
|
this.shadow.append(fundInvestorTemplate.content.cloneNode(true))
|
|
this.amountInvested
|
|
this.netValue
|
|
}
|
|
set data(obj){
|
|
const {
|
|
href,
|
|
floId,
|
|
amountInvested,
|
|
netValue,
|
|
} = obj
|
|
|
|
this.amountInvested = amountInvested
|
|
this.netValue = netValue
|
|
|
|
for (let item in this.amountInvested){
|
|
this.amountInvested[item] = parseFloat(this.amountInvested[item])
|
|
}
|
|
|
|
for (let item in this.netValue){
|
|
this.netValue[item] = parseFloat(this.netValue[item])
|
|
}
|
|
|
|
this.dataset.floId = floId
|
|
|
|
this.shadow.querySelector('.flo-id').textContent = floId
|
|
this.toggleCurrency()
|
|
}
|
|
|
|
toggleCurrency = () => {
|
|
this.shadow.querySelector('.amount-invested').textContent = `${this.amountInvested[preferredCurrency].toLocaleString(`en-${preferredCurrency.substring(0,2)}`, {style: 'currency', currency: preferredCurrency})}`
|
|
this.shadow.querySelector('.net-value').textContent = `${this.netValue[preferredCurrency].toLocaleString(`en-${preferredCurrency.substring(0,2)}`, {style: 'currency', currency: preferredCurrency})}`
|
|
}
|
|
|
|
connectedCallback(){
|
|
document.getElementById('currency_selector').addEventListener('change', this.toggleCurrency)
|
|
}
|
|
|
|
disconnectedCallback(){
|
|
document.getElementById('currency_selector').removeEventListener('change', this.toggleCurrency)
|
|
}
|
|
})
|
|
|
|
//Input
|
|
const smInput = document.createElement('template')
|
|
smInput.innerHTML = `
|
|
<style>
|
|
*{
|
|
padding: 0;
|
|
margin: 0;
|
|
-webkit-box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
}
|
|
input[type="search"]::-webkit-search-decoration,
|
|
input[type="search"]::-webkit-search-cancel-button,
|
|
input[type="search"]::-webkit-search-results-button,
|
|
input[type="search"]::-webkit-search-results-decoration { display: none; }
|
|
input[type=number] {
|
|
-moz-appearance:textfield;
|
|
}
|
|
input[type=number]::-webkit-inner-spin-button,
|
|
input[type=number]::-webkit-outer-spin-button {
|
|
-webkit-appearance: none;
|
|
-moz-appearance: none;
|
|
appearance: none;
|
|
margin: 0;
|
|
}
|
|
input::-ms-reveal,
|
|
input::-ms-clear {
|
|
display: none;
|
|
}
|
|
input:invalid{
|
|
outline: none;
|
|
-webkit-box-shadow: none;
|
|
box-shadow: none;
|
|
}
|
|
::-moz-focus-inner{
|
|
border: none;
|
|
}
|
|
:host{
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
--font-size: 1rem;
|
|
--border-radius: 0.3rem;
|
|
--padding: 0.7rem 1rem;
|
|
--background: rgba(var(--text-color), 0.06);
|
|
--active-placeholder-color: var(--accent-color);
|
|
}
|
|
.hide{
|
|
opacity: 0 !important;
|
|
pointer-events: none !important;
|
|
}
|
|
.hide-completely{
|
|
display: none;
|
|
}
|
|
.icon {
|
|
fill: rgba(var(--text-color), 0.6);
|
|
height: 1.4rem;
|
|
width: 1.4rem;
|
|
border-radius: 1rem;
|
|
cursor: pointer;
|
|
min-width: 0;
|
|
}
|
|
|
|
:host(.round) .input{
|
|
border-radius: 10rem;
|
|
}
|
|
.input {
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
cursor: text;
|
|
min-width: 0;
|
|
text-align: left;
|
|
-webkit-box-align: center;
|
|
-ms-flex-align: center;
|
|
align-items: center;
|
|
position: relative;
|
|
gap: 0.5rem;
|
|
padding: var(--padding);
|
|
border-radius: var(--border-radius);
|
|
-webkit-transition: opacity 0.3s;
|
|
-o-transition: opacity 0.3s;
|
|
transition: opacity 0.3s;
|
|
background: var(--background);
|
|
width: 100%;
|
|
outline: none;
|
|
}
|
|
.input.readonly .clear{
|
|
opacity: 0 !important;
|
|
margin-right: -2rem;
|
|
pointer-events: none !important;
|
|
}
|
|
.readonly{
|
|
pointer-events: none;
|
|
}
|
|
.input:focus-within:not(.readonly){
|
|
box-shadow: 0 0 0 0.1rem var(--accent-color) inset !important;
|
|
}
|
|
.disabled{
|
|
pointer-events: none;
|
|
opacity: 0.6;
|
|
}
|
|
.label {
|
|
opacity: .7;
|
|
font-size: var(--font-size);
|
|
font-weight: var(--font-weight);
|
|
position: absolute;
|
|
top: 0;
|
|
-webkit-transition: -webkit-transform 0.3s;
|
|
transition: -webkit-transform 0.3s;
|
|
-o-transition: transform 0.3s;
|
|
transition: transform 0.3s;
|
|
transition: transform 0.3s, -webkit-transform 0.3s;
|
|
-webkit-transform-origin: left;
|
|
-ms-transform-origin: left;
|
|
transform-origin: left;
|
|
pointer-events: none;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
-o-text-overflow: ellipsis;
|
|
text-overflow: ellipsis;
|
|
width: 100%;
|
|
user-select: none;
|
|
will-change: transform;
|
|
}
|
|
.outer-container{
|
|
position: relative;
|
|
width: 100%;
|
|
}
|
|
.container{
|
|
width: 100%;
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
position: relative;
|
|
-webkit-box-align: center;
|
|
-ms-flex-align: center;
|
|
align-items: center;
|
|
-webkit-box-flex: 1;
|
|
-ms-flex: 1;
|
|
flex: 1;
|
|
}
|
|
input{
|
|
font-size: var(--font-size);
|
|
border: none;
|
|
background: transparent;
|
|
outline: none;
|
|
color: rgba(var(--text-color), 1);
|
|
width: 100%;
|
|
font-weight: var(--font-weight);
|
|
}
|
|
:host(:not(.outlined)) .animate-label .container input {
|
|
-webkit-transform: translateY(0.6rem);
|
|
-ms-transform: translateY(0.6rem);
|
|
transform: translateY(0.6rem);
|
|
}
|
|
|
|
:host(:not(.outlined)) .animate-label .label {
|
|
-webkit-transform: translateY(-0.7em) scale(0.8);
|
|
-ms-transform: translateY(-0.7em) scale(0.8);
|
|
transform: translateY(-0.7em) scale(0.8);
|
|
opacity: 1;
|
|
color: var(--accent-color)
|
|
}
|
|
:host(.outlined) .input {
|
|
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 0.4) inset;
|
|
background: rgba(var(--foreground-color), 1);
|
|
}
|
|
:host(.outlined) .label {
|
|
width: max-content;
|
|
margin-left: -0.5rem;
|
|
padding: 0 0.5rem;
|
|
}
|
|
:host(.outlined) .animate-label .label {
|
|
-webkit-transform: translate(0.1rem, -1.3rem) scale(0.8);
|
|
-ms-transform: translate(0.1rem, -1.3rem) scale(0.8);
|
|
transform: translate(0.1rem, -1.3rem) scale(0.8);
|
|
opacity: 1;
|
|
background: rgba(var(--foreground-color), 1);
|
|
}
|
|
.animate-label:focus-within:not(.readonly) .label{
|
|
color: var(--active-placeholder-color);
|
|
}
|
|
.feedback-text{
|
|
font-size: 0.9rem;
|
|
width: 100%;
|
|
color: var(--error-color);
|
|
padding: 0.6rem 1rem;
|
|
text-align: left;
|
|
}
|
|
.feedback-text:empty{
|
|
padding: 0;
|
|
}
|
|
@media (any-hover: hover){
|
|
.icon:hover{
|
|
background: rgba(var(--text-color), 0.1);
|
|
}
|
|
}
|
|
</style>
|
|
<div class="outer-container">
|
|
<label part="input" class="input">
|
|
<slot name="icon"></slot>
|
|
<div class="container">
|
|
<input/>
|
|
<div part="placeholder" class="label"></div>
|
|
</div>
|
|
<svg class="icon clear hide" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-11.414L9.172 7.757 7.757 9.172 10.586 12l-2.829 2.828 1.415 1.415L12 13.414l2.828 2.829 1.415-1.415L13.414 12l2.829-2.828-1.415-1.415L12 10.586z"/></svg>
|
|
</label>
|
|
<div class="feedback-text"></div>
|
|
</div>
|
|
`;
|
|
customElements.define('sm-input',
|
|
class extends HTMLElement {
|
|
|
|
constructor() {
|
|
super()
|
|
this.attachShadow({
|
|
mode: 'open'
|
|
}).append(smInput.content.cloneNode(true))
|
|
}
|
|
|
|
static get observedAttributes() {
|
|
return ['placeholder']
|
|
}
|
|
|
|
get value() {
|
|
return this.shadowRoot.querySelector('input').value
|
|
}
|
|
|
|
set value(val) {
|
|
this.shadowRoot.querySelector('input').value = val;
|
|
this.checkInput()
|
|
this.fireEvent()
|
|
}
|
|
|
|
get placeholder() {
|
|
return this.getAttribute('placeholder')
|
|
}
|
|
|
|
set placeholder(val) {
|
|
this.setAttribute('placeholder', val)
|
|
}
|
|
|
|
get type() {
|
|
return this.getAttribute('type')
|
|
}
|
|
|
|
get isValid() {
|
|
return this.shadowRoot.querySelector('input').checkValidity()
|
|
}
|
|
|
|
get validity() {
|
|
return this.shadowRoot.querySelector('input').validity
|
|
}
|
|
|
|
set disabled(value) {
|
|
if (value)
|
|
this.shadowRoot.querySelector('.input').classList.add('disabled')
|
|
else
|
|
this.shadowRoot.querySelector('.input').classList.remove('disabled')
|
|
}
|
|
set readOnly(value) {
|
|
if (value) {
|
|
this.shadowRoot.querySelector('input').setAttribute('readonly', '')
|
|
this.shadowRoot.querySelector('.input').classList.add('readonly')
|
|
} else {
|
|
this.shadowRoot.querySelector('input').removeAttribute('readonly')
|
|
this.shadowRoot.querySelector('.input').classList.remove('readonly')
|
|
}
|
|
}
|
|
|
|
setValidity = (message) => {
|
|
this.feedbackText.textContent = message
|
|
}
|
|
|
|
showValidity = () => {
|
|
this.feedbackText.classList.remove('hide-completely')
|
|
}
|
|
|
|
hideValidity = () => {
|
|
this.feedbackText.classList.add('hide-completely')
|
|
}
|
|
|
|
focusIn = () => {
|
|
this.input.focus()
|
|
}
|
|
|
|
focusOut = () => {
|
|
this.input.blur()
|
|
}
|
|
|
|
fireEvent = () => {
|
|
let event = new Event('input', {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
composed: true
|
|
});
|
|
this.dispatchEvent(event);
|
|
}
|
|
|
|
checkInput = (e) => {
|
|
if (!this.readonly) {
|
|
if (this.input.value !== '') {
|
|
this.clearBtn.classList.remove('hide')
|
|
} else {
|
|
this.clearBtn.classList.add('hide')
|
|
}
|
|
}
|
|
if (!this.hasAttribute('placeholder') || this.getAttribute('placeholder') === '') return;
|
|
if (this.input.value !== '') {
|
|
if (this.animate)
|
|
this.inputParent.classList.add('animate-label')
|
|
else
|
|
this.label.classList.add('hide')
|
|
} else {
|
|
if (this.animate)
|
|
this.inputParent.classList.remove('animate-label')
|
|
else
|
|
this.label.classList.remove('hide')
|
|
}
|
|
}
|
|
|
|
|
|
connectedCallback() {
|
|
this.inputParent = this.shadowRoot.querySelector('.input')
|
|
this.clearBtn = this.shadowRoot.querySelector('.clear')
|
|
this.label = this.shadowRoot.querySelector('.label')
|
|
this.feedbackText = this.shadowRoot.querySelector('.feedback-text')
|
|
this.valueChanged = false;
|
|
this.readonly = false
|
|
this.isNumeric = false
|
|
this.min
|
|
this.max
|
|
this.animate = this.hasAttribute('animate')
|
|
this.input = this.shadowRoot.querySelector('input')
|
|
this.shadowRoot.querySelector('.label').textContent = this.getAttribute('placeholder')
|
|
if (this.hasAttribute('value')) {
|
|
this.input.value = this.getAttribute('value')
|
|
this.checkInput()
|
|
}
|
|
if (this.hasAttribute('required')) {
|
|
this.input.setAttribute('required', '')
|
|
}
|
|
if (this.hasAttribute('min')) {
|
|
let minValue = this.getAttribute('min')
|
|
this.input.setAttribute('min', minValue)
|
|
this.min = parseInt(minValue)
|
|
}
|
|
if (this.hasAttribute('max')) {
|
|
let maxValue = this.getAttribute('max')
|
|
this.input.setAttribute('max', maxValue)
|
|
this.max = parseInt(maxValue)
|
|
}
|
|
if (this.hasAttribute('minlength')) {
|
|
const minValue = this.getAttribute('minlength')
|
|
this.input.setAttribute('minlength', minValue)
|
|
}
|
|
if (this.hasAttribute('maxlength')) {
|
|
const maxValue = this.getAttribute('maxlength')
|
|
this.input.setAttribute('maxlength', maxValue)
|
|
}
|
|
if (this.hasAttribute('step')) {
|
|
const steps = this.getAttribute('step')
|
|
this.input.setAttribute('step', steps)
|
|
}
|
|
if (this.hasAttribute('pattern')) {
|
|
this.input.setAttribute('pattern', this.getAttribute('pattern'))
|
|
}
|
|
if (this.hasAttribute('readonly')) {
|
|
this.input.setAttribute('readonly', '')
|
|
this.readonly = true
|
|
}
|
|
if (this.hasAttribute('disabled')) {
|
|
this.inputParent.classList.add('disabled')
|
|
}
|
|
if (this.hasAttribute('error-text')) {
|
|
this.feedbackText.textContent = this.getAttribute('error-text')
|
|
}
|
|
if (this.hasAttribute('type')) {
|
|
if (this.getAttribute('type') === 'number') {
|
|
this.input.setAttribute('inputmode', 'numeric')
|
|
this.input.setAttribute('type', 'number')
|
|
this.isNumeric = true
|
|
} else
|
|
this.input.setAttribute('type', this.getAttribute('type'))
|
|
} else
|
|
this.input.setAttribute('type', 'text')
|
|
this.input.addEventListener('input', e => {
|
|
this.checkInput(e)
|
|
})
|
|
this.clearBtn.addEventListener('click', e => {
|
|
this.value = ''
|
|
})
|
|
}
|
|
|
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
if (oldValue !== newValue) {
|
|
if (name === 'placeholder') {
|
|
this.shadowRoot.querySelector('.label').textContent = newValue;
|
|
this.setAttribute('aria-label', newValue);
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
|
|
|
|
const smSelect = document.createElement('template')
|
|
smSelect.innerHTML = `
|
|
<style>
|
|
*{
|
|
padding: 0;
|
|
margin: 0;
|
|
-webkit-box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
}
|
|
:host{
|
|
display: -webkit-inline-box;
|
|
display: -ms-inline-flexbox;
|
|
display: inline-flex;
|
|
--max-height: auto;
|
|
--border: solid 1px rgba(var(--text-color), 0.2);
|
|
--arrow-fill: rgba(var(--text-color), 0.7);
|
|
--selection-font-size: inherit;
|
|
}
|
|
.icon {
|
|
height: 1.4rem;
|
|
width: 1.4rem;
|
|
margin-left: 0.5rem;
|
|
fill: var(--arrow-fill);
|
|
}
|
|
.hide{
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
.select{
|
|
position: relative;
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
-webkit-box-orient: vertical;
|
|
-webkit-box-direction: normal;
|
|
-ms-flex-direction: column;
|
|
flex-direction: column;
|
|
cursor: pointer;
|
|
width: 100%;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
.option-text{
|
|
overflow: hidden;
|
|
-o-text-overflow: ellipsis;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font-weight: 700;
|
|
font-size: var(--selection-font-size);
|
|
}
|
|
.selection{
|
|
border-radius: 0.3rem;
|
|
display: -ms-grid;
|
|
display: grid;
|
|
-ms-grid-columns: 1fr auto;
|
|
grid-template-columns: 1fr auto;
|
|
grid-template-areas: 'heading heading' '. .';
|
|
padding: 0.4rem 1rem;
|
|
background: rgba(var(--text-color), 0.06);
|
|
border: var(--border);
|
|
-webkit-box-align: center;
|
|
-ms-flex-align: center;
|
|
align-items: center;
|
|
outline: none;
|
|
}
|
|
.selection:focus-visible{
|
|
-webkit-box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1);
|
|
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1);
|
|
}
|
|
:host([align-select="left"]) .options{
|
|
left: 0;
|
|
}
|
|
:host([align-select="right"]) .options{
|
|
right: 0;
|
|
}
|
|
.options{
|
|
top: 100%;
|
|
margin-top: 0.5rem;
|
|
overflow: hidden auto;
|
|
position: absolute;
|
|
grid-area: options;
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
-webkit-box-orient: vertical;
|
|
-webkit-box-direction: normal;
|
|
-ms-flex-direction: column;
|
|
flex-direction: column;
|
|
min-width: 100%;
|
|
max-height: var(--max-height);
|
|
background: rgba(var(--foreground-color), 1);
|
|
-webkit-transition: opacity 0.3s, top 0.3s;
|
|
-o-transition: opacity 0.3s, top 0.3s;
|
|
transition: opacity 0.3s, top 0.3s;
|
|
border: solid 1px rgba(var(--text-color), 0.2);
|
|
border-radius: 0.3rem;
|
|
z-index: 2;
|
|
-webkit-box-shadow: 0.4rem 0.8rem 1.2rem #00000030;
|
|
box-shadow: 0.4rem 0.8rem 1.2rem #00000030;
|
|
}
|
|
.rotate{
|
|
-webkit-transform: rotate(180deg);
|
|
-ms-transform: rotate(180deg);
|
|
transform: rotate(180deg)
|
|
}
|
|
</style>
|
|
<div class="select" >
|
|
<div class="selection" tabindex="0">
|
|
<div class="option-text"></div>
|
|
<svg class="icon toggle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 13.172l4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z"/></svg>
|
|
</div>
|
|
<div part="options" aria-hidden="true" class="options hide">
|
|
<slot></slot>
|
|
</div>
|
|
</div>`;
|
|
customElements.define('sm-select', class extends HTMLElement {
|
|
constructor() {
|
|
super()
|
|
this.attachShadow({
|
|
mode: 'open'
|
|
}).append(smSelect.content.cloneNode(true))
|
|
this.availableOptions
|
|
this.optionList = this.shadowRoot.querySelector('.options')
|
|
this.chevron = this.shadowRoot.querySelector('.toggle')
|
|
this.optionText = this.shadowRoot.querySelector('.option-text')
|
|
}
|
|
static get observedAttributes() {
|
|
return ['value']
|
|
}
|
|
get value() {
|
|
return this.getAttribute('value')
|
|
}
|
|
set value(val) {
|
|
this.setAttribute('value', val)
|
|
this.optionText.textContent = val;
|
|
}
|
|
|
|
open = () => {
|
|
this.optionList.classList.remove('hide')
|
|
this.optionList.animate(this.slideDown, this.animationOptions)
|
|
this.availableOptions.forEach((element, index) => {
|
|
element.setAttribute('tabindex', "0");
|
|
})
|
|
this.chevron.classList.add('rotate')
|
|
this.isOpen = true
|
|
}
|
|
|
|
collapse = () => {
|
|
this.optionList.animate(this.slideUp, this.animationOptions)
|
|
this.optionList.setAttribute('aria-hidden', 'true')
|
|
this.availableOptions.forEach((element, index) => {
|
|
element.setAttribute('tabindex', "-1");
|
|
})
|
|
this.optionList.classList.add('hide')
|
|
this.chevron.classList.remove('rotate')
|
|
this.isOpen = false
|
|
}
|
|
connectedCallback() {
|
|
let slot = this.shadowRoot.querySelector('.options slot'),
|
|
selection = this.shadowRoot.querySelector('.selection'),
|
|
previousOption
|
|
this.isOpen = false;
|
|
this.slideDown = [{
|
|
transform: `translateY(-0.5rem)`
|
|
},
|
|
{
|
|
transform: `translateY(0)`
|
|
}
|
|
],
|
|
this.slideUp = [{
|
|
transform: `translateY(0)`
|
|
},
|
|
{
|
|
transform: `translateY(-0.5rem)`
|
|
}
|
|
],
|
|
this.animationOptions = {
|
|
duration: 300,
|
|
fill: "forwards",
|
|
easing: 'ease'
|
|
}
|
|
selection.addEventListener('click', e => {
|
|
if (!this.isOpen) {
|
|
this.open()
|
|
} else {
|
|
this.collapse()
|
|
}
|
|
})
|
|
selection.addEventListener('keydown', e => {
|
|
if (e.code === 'ArrowDown' || e.code === 'ArrowRight') {
|
|
e.preventDefault()
|
|
this.availableOptions[0].focus()
|
|
}
|
|
if (e.code === 'Enter' || e.code === 'Space')
|
|
if (!this.isOpen) {
|
|
this.open()
|
|
} else {
|
|
this.collapse()
|
|
}
|
|
})
|
|
this.optionList.addEventListener('keydown', e => {
|
|
if (e.code === 'ArrowUp' || e.code === 'ArrowRight') {
|
|
e.preventDefault()
|
|
if (document.activeElement.previousElementSibling) {
|
|
document.activeElement.previousElementSibling.focus()
|
|
}
|
|
}
|
|
if (e.code === 'ArrowDown' || e.code === 'ArrowLeft') {
|
|
e.preventDefault()
|
|
if (document.activeElement.nextElementSibling)
|
|
document.activeElement.nextElementSibling.focus()
|
|
}
|
|
})
|
|
this.addEventListener('optionSelected', e => {
|
|
if (previousOption !== e.target) {
|
|
this.setAttribute('value', e.detail.value)
|
|
this.optionText.textContent = e.detail.text;
|
|
this.dispatchEvent(new CustomEvent('change', {
|
|
bubbles: true,
|
|
composed: true,
|
|
detail: {
|
|
value: e.detail.value
|
|
}
|
|
}))
|
|
if (previousOption) {
|
|
previousOption.classList.remove('check-selected')
|
|
}
|
|
previousOption = e.target;
|
|
}
|
|
if (!e.detail.switching)
|
|
this.collapse()
|
|
|
|
e.target.classList.add('check-selected')
|
|
})
|
|
slot.addEventListener('slotchange', e => {
|
|
this.availableOptions = slot.assignedElements()
|
|
if (this.availableOptions[0]) {
|
|
this.availableOptions.forEach(option => {
|
|
if(option.hasAttribute('selected')){
|
|
this.optionText.textContent = option.textContent
|
|
this.setAttribute('value', option.getAttribute('value'))
|
|
option.classList.add('check-selected')
|
|
previousOption = option
|
|
}
|
|
})
|
|
}
|
|
});
|
|
document.addEventListener('mousedown', e => {
|
|
if (!this.contains(e.target) && this.isOpen) {
|
|
this.collapse()
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
// option
|
|
const smOption = document.createElement('template')
|
|
smOption.innerHTML = `
|
|
<style>
|
|
*{
|
|
padding: 0;
|
|
margin: 0;
|
|
-webkit-box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
}
|
|
:host{
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
--color: inherit;
|
|
}
|
|
.option{
|
|
min-width: 100%;
|
|
padding: 0.8rem 1.2rem;
|
|
cursor: pointer;
|
|
overflow-wrap: break-word;
|
|
outline: none;
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
color: var(--color);
|
|
-webkit-box-align: center;
|
|
-ms-flex-align: center;
|
|
align-items: center;
|
|
}
|
|
:host(:focus){
|
|
outline: none;
|
|
background: rgba(var(--text-color), 0.1);
|
|
}
|
|
:host(:focus) .option .icon{
|
|
opacity: 0.4
|
|
}
|
|
:host(.check-selected) .icon{
|
|
opacity: 1 !important
|
|
}
|
|
.icon {
|
|
margin-right: 0.8rem;
|
|
fill: none;
|
|
height: 0.8rem;
|
|
width: 0.8rem;
|
|
stroke: rgba(var(--text-color), 0.7);
|
|
stroke-width: 10;
|
|
overflow: visible;
|
|
stroke-linecap: round;
|
|
border-radius: 1rem;
|
|
stroke-linejoin: round;
|
|
opacity: 0;
|
|
}
|
|
@media (hover: hover){
|
|
.option:hover{
|
|
background: rgba(var(--text-color), 0.1);
|
|
}
|
|
.option:hover .icon{
|
|
opacity: 0.4
|
|
}
|
|
}
|
|
</style>
|
|
<div class="option">
|
|
<svg class="icon" viewBox="0 0 64 64">
|
|
<polyline points="0.35 31.82 21.45 52.98 63.65 10.66"/>
|
|
</svg>
|
|
<slot></slot>
|
|
</div>`;
|
|
customElements.define('sm-option', class extends HTMLElement {
|
|
constructor() {
|
|
super()
|
|
this.attachShadow({
|
|
mode: 'open'
|
|
}).append(smOption.content.cloneNode(true))
|
|
}
|
|
|
|
sendDetails(switching) {
|
|
let optionSelected = new CustomEvent('optionSelected', {
|
|
bubbles: true,
|
|
composed: true,
|
|
detail: {
|
|
text: this.textContent,
|
|
value: this.getAttribute('value'),
|
|
switching: switching
|
|
}
|
|
})
|
|
this.dispatchEvent(optionSelected)
|
|
}
|
|
|
|
connectedCallback() {
|
|
let validKey = [
|
|
'ArrowUp',
|
|
'ArrowDown',
|
|
'ArrowLeft',
|
|
'ArrowRight'
|
|
]
|
|
this.addEventListener('click', e => {
|
|
this.sendDetails()
|
|
})
|
|
this.addEventListener('keyup', e => {
|
|
if (e.code === 'Enter' || e.code === 'Space') {
|
|
e.preventDefault()
|
|
this.sendDetails(false)
|
|
}
|
|
if (validKey.includes(e.code)) {
|
|
e.preventDefault()
|
|
this.sendDetails(true)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
const smSwitch = document.createElement('template')
|
|
smSwitch.innerHTML = `
|
|
<style>
|
|
*{
|
|
-webkit-box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
|
|
:host{
|
|
display: -webkit-inline-box;
|
|
display: -ms-inline-flexbox;
|
|
display: inline-flex;
|
|
}
|
|
label{
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
-webkit-box-align: center;
|
|
-ms-flex-align: center;
|
|
align-items: center;
|
|
width: 100%;
|
|
outline: none;
|
|
cursor: pointer;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
:host(:not([disabled])) label:focus-visible{
|
|
-webkit-box-shadow: 0 0 0 0.1rem var(--accent-color);
|
|
box-shadow: 0 0 0 0.1rem var(--accent-color);
|
|
}
|
|
:host([disabled]) {
|
|
cursor: not-allowed;
|
|
opacity: 0.6;
|
|
pointer-events: none;
|
|
}
|
|
.switch {
|
|
position: relative;
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
-webkit-box-align: center;
|
|
-ms-flex-align: center;
|
|
align-items: center;
|
|
width: 2.4rem;
|
|
flex-shrink: 0;
|
|
margin-left: auto;
|
|
padding: 0.2rem;
|
|
cursor: pointer;
|
|
border-radius: 2rem;
|
|
}
|
|
|
|
input {
|
|
display: none;
|
|
}
|
|
|
|
.track {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
height: 1.4rem;
|
|
-webkit-transition: background 0.3s;
|
|
-o-transition: background 0.3s;
|
|
transition: background 0.3s;
|
|
background: rgba(var(--text-color), 0.4);
|
|
-webkit-box-shadow: 0 0.1rem 0.3rem #00000040 inset;
|
|
box-shadow: 0 0.1rem 0.3rem #00000040 inset;
|
|
border-radius: 1rem;
|
|
}
|
|
|
|
.switch:active .button::after,
|
|
.switch:focus .button::after{
|
|
opacity: 1
|
|
}
|
|
.switch:focus-visible .button::after{
|
|
opacity: 1
|
|
}
|
|
|
|
.button::after{
|
|
content: '';
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
position: absolute;
|
|
height: 2.6rem;
|
|
width: 2.6rem;
|
|
background: rgba(var(--text-color), 0.2);
|
|
border-radius: 2rem;
|
|
opacity: 0;
|
|
-webkit-transition: opacity 0.3s;
|
|
-o-transition: opacity 0.3s;
|
|
transition: opacity 0.3s;
|
|
}
|
|
|
|
.button {
|
|
position: relative;
|
|
display: -webkit-inline-box;
|
|
display: -ms-inline-flexbox;
|
|
display: inline-flex;
|
|
height: 1rem;
|
|
width: 1rem;
|
|
-webkit-box-pack: center;
|
|
-ms-flex-pack: center;
|
|
justify-content: center;
|
|
-webkit-box-align: center;
|
|
-ms-flex-align: center;
|
|
align-items: center;
|
|
border-radius: 1rem;
|
|
-webkit-box-shadow: 0 0.1rem 0.4rem #00000060;
|
|
box-shadow: 0 0.1rem 0.4rem #00000060;
|
|
-webkit-transition: -webkit-transform 0.3s;
|
|
transition: -webkit-transform 0.3s;
|
|
-o-transition: transform 0.3s;
|
|
transition: transform 0.3s;
|
|
transition: transform 0.3s, -webkit-transform 0.3s;
|
|
border: solid 0.3rem white;
|
|
}
|
|
|
|
input:checked ~ .button {
|
|
-webkit-transform: translateX(100%);
|
|
-ms-transform: translateX(100%);
|
|
transform: translateX(100%);
|
|
}
|
|
|
|
input:checked ~ .track {
|
|
background: var(--accent-color);
|
|
}
|
|
</style>
|
|
<label tabindex="0">
|
|
<slot name="left"></slot>
|
|
<div part="switch" class="switch">
|
|
<input type="checkbox">
|
|
<div class="track"></div>
|
|
<div class="button"></div>
|
|
</div>
|
|
</label>`
|
|
|
|
customElements.define('sm-switch', class extends HTMLElement {
|
|
constructor() {
|
|
super()
|
|
this.attachShadow({
|
|
mode: 'open'
|
|
}).append(smSwitch.content.cloneNode(true))
|
|
this.switch = this.shadowRoot.querySelector('.switch');
|
|
this.input = this.shadowRoot.querySelector('input')
|
|
this.isChecked = false
|
|
this.isDisabled = false
|
|
}
|
|
|
|
static get observedAttributes() {
|
|
return ['disabled', 'checked']
|
|
}
|
|
|
|
get disabled() {
|
|
return this.isDisabled
|
|
}
|
|
|
|
set disabled(val) {
|
|
if (val) {
|
|
this.setAttribute('disabled', '')
|
|
} else {
|
|
this.removeAttribute('disabled')
|
|
}
|
|
}
|
|
|
|
get checked() {
|
|
return this.isChecked
|
|
}
|
|
|
|
set checked(value) {
|
|
if (value) {
|
|
this.setAttribute('checked', '')
|
|
} else {
|
|
this.removeAttribute('checked')
|
|
}
|
|
}
|
|
|
|
dispatch = () => {
|
|
this.dispatchEvent(new CustomEvent('change', {
|
|
bubbles: true,
|
|
composed: true,
|
|
detail: {
|
|
value: this.isChecked,
|
|
checked: this.isChecked
|
|
}
|
|
}))
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.addEventListener('keyup', e => {
|
|
if ((e.code === "Enter" || e.code === "Space") && !this.isDisabled) {
|
|
this.input.click()
|
|
}
|
|
})
|
|
this.input.addEventListener('click', e => {
|
|
if (this.input.checked)
|
|
this.checked = true
|
|
else
|
|
this.checked = false
|
|
this.dispatch()
|
|
})
|
|
}
|
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
if (oldValue !== newValue) {
|
|
if (name === 'disabled') {
|
|
if (this.hasAttribute('disabled')) {
|
|
this.disabled = true
|
|
}
|
|
else {
|
|
this.disabled = false
|
|
}
|
|
}
|
|
else if (name === 'checked') {
|
|
if (this.hasAttribute('checked')) {
|
|
this.isChecked = true
|
|
this.input.checked = true
|
|
}
|
|
else {
|
|
this.isChecked = false
|
|
this.input.checked = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
})
|
|
|
|
//notifications
|
|
|
|
const smNotifications = document.createElement('template')
|
|
smNotifications.innerHTML = `
|
|
<style>
|
|
*{
|
|
padding: 0;
|
|
margin: 0;
|
|
-webkit-box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
}
|
|
:host{
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
}
|
|
.hide{
|
|
opacity: 0 !important;
|
|
pointer-events: none !important;
|
|
}
|
|
.notification-panel{
|
|
display: -ms-grid;
|
|
display: grid;
|
|
width: 100%;
|
|
position: fixed;
|
|
top: 0;
|
|
right: 0;
|
|
z-index: 100;
|
|
max-height: 100%;
|
|
overflow: hidden auto;
|
|
-ms-scroll-chaining: none;
|
|
overscroll-behavior: contain;
|
|
}
|
|
.no-transformations{
|
|
-webkit-transform: none;
|
|
-ms-transform: none;
|
|
transform: none;
|
|
opacity: 1;
|
|
}
|
|
.notification-panel:empty{
|
|
display:none;
|
|
}
|
|
.notification{
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
opacity: 0;
|
|
padding: 1rem 1.5rem;
|
|
margin-bottom: 0.5rem;
|
|
-webkit-transform: translateY(-1rem);
|
|
-ms-transform: translateY(-1rem);
|
|
transform: translateY(-1rem);
|
|
position: relative;
|
|
border-radius: 0.3rem;
|
|
-webkit-box-shadow: 0 0.1rem 0.2rem rgba(0, 0, 0, 0.1),
|
|
0.5rem 1rem 2rem rgba(0, 0, 0, 0.1);
|
|
box-shadow: 0 0.1rem 0.2rem rgba(0, 0, 0, 0.1),
|
|
0.5rem 1rem 2rem rgba(0, 0, 0, 0.1);
|
|
background: rgba(var(--foreground-color), 1);
|
|
-webkit-transition: height 0.3s, opacity 0.3s, -webkit-transform 0.3s;
|
|
transition: height 0.3s, opacity 0.3s, -webkit-transform 0.3s;
|
|
-o-transition: height 0.3s, transform 0.3s, opacity 0.3s;
|
|
transition: height 0.3s, transform 0.3s, opacity 0.3s;
|
|
transition: height 0.3s, transform 0.3s, opacity 0.3s, -webkit-transform 0.3s;
|
|
overflow: hidden;
|
|
overflow-wrap: break-word;
|
|
word-wrap: break-word;
|
|
-ms-word-break: break-all;
|
|
word-break: break-all;
|
|
word-break: break-word;
|
|
-ms-hyphens: auto;
|
|
-webkit-hyphens: auto;
|
|
hyphens: auto;
|
|
max-width: 100%;
|
|
touch-action: none;
|
|
}
|
|
h4:first-letter,
|
|
p:first-letter{
|
|
text-transform: uppercase;
|
|
}
|
|
h4{
|
|
font-weight: 400;
|
|
}
|
|
p{
|
|
line-height: 1.6;
|
|
-webkit-box-flex: 1;
|
|
-ms-flex: 1;
|
|
flex: 1;
|
|
color: rgba(var(--text-color), 0.9);
|
|
overflow-wrap: break-word;
|
|
overflow-wrap: break-word;
|
|
word-wrap: break-word;
|
|
-ms-word-break: break-all;
|
|
word-break: break-all;
|
|
word-break: break-word;
|
|
-ms-hyphens: auto;
|
|
-webkit-hyphens: auto;
|
|
hyphens: auto;
|
|
max-width: 100%;
|
|
}
|
|
.notification:last-of-type{
|
|
margin-bottom: 0;
|
|
}
|
|
.icon {
|
|
fill: none;
|
|
height: 1.6rem;
|
|
width: 1.6rem;
|
|
stroke: rgba(var(--text-color), 0.7);
|
|
overflow: visible;
|
|
stroke-linecap: round;
|
|
border-radius: 1rem;
|
|
stroke-linejoin: round;
|
|
cursor: pointer;
|
|
}
|
|
.error-icon{
|
|
stroke: #E53935;
|
|
}
|
|
.success-icon{
|
|
stroke: #00C853;
|
|
}
|
|
.close{
|
|
margin-left: 1rem;
|
|
padding: 0.5rem;
|
|
stroke-width: 10;
|
|
}
|
|
.notification-icon{
|
|
height: 1.4rem;
|
|
width: 1.4rem;
|
|
margin: 0.3em 1rem 0 0;
|
|
stroke-width: 6;
|
|
}
|
|
@media screen and (min-width: 640px){
|
|
.notification-panel{
|
|
max-width: 28rem;
|
|
width: max-content;
|
|
-webkit-box-pack: end;
|
|
-ms-flex-pack: end;
|
|
justify-content: flex-end;
|
|
}
|
|
.notification{
|
|
-ms-grid-column-align: end;
|
|
justify-self: end;
|
|
width: auto;
|
|
margin-right: 1.5rem;
|
|
margin-bottom: 1rem;
|
|
border-bottom: none;
|
|
border: solid 1px rgba(var(--text-color), 0.2);
|
|
-webkit-transform: translateX(1rem);
|
|
-ms-transform: translateX(1rem);
|
|
transform: translateX(1rem);
|
|
}
|
|
}
|
|
@media screen and (any-hover: none){
|
|
.close{
|
|
display: none
|
|
}
|
|
}
|
|
</style>
|
|
<div class="notification-panel"></div>
|
|
`
|
|
|
|
customElements.define('sm-notifications', class extends HTMLElement {
|
|
constructor() {
|
|
super()
|
|
this.shadow = this.attachShadow({
|
|
mode: 'open'
|
|
}).append(smNotifications.content.cloneNode(true))
|
|
}
|
|
|
|
handleTouchStart = (e) => {
|
|
this.notification = e.target.closest('.notification')
|
|
this.touchStartX = e.changedTouches[0].clientX
|
|
this.notification.style.transition = 'initial'
|
|
this.touchStartTime = e.timeStamp
|
|
}
|
|
|
|
handleTouchMove = (e) => {
|
|
e.preventDefault()
|
|
if (this.touchStartX < e.changedTouches[0].clientX) {
|
|
this.offset = e.changedTouches[0].clientX - this.touchStartX;
|
|
this.touchEndAnimataion = requestAnimationFrame(this.movePopup)
|
|
} else {
|
|
this.offset = -(this.touchStartX - e.changedTouches[0].clientX);
|
|
this.touchEndAnimataion = requestAnimationFrame(this.movePopup)
|
|
}
|
|
}
|
|
|
|
handleTouchEnd = (e) => {
|
|
this.notification.style.transition = 'transform 0.3s, opacity 0.3s'
|
|
this.touchEndTime = e.timeStamp
|
|
cancelAnimationFrame(this.touchEndAnimataion)
|
|
this.touchEndX = e.changedTouches[0].clientX
|
|
if (this.touchEndTime - this.touchStartTime > 200) {
|
|
if (this.touchEndX - this.touchStartX > this.threshold) {
|
|
this.removeNotification(this.notification)
|
|
} else if (this.touchStartX - this.touchEndX > this.threshold) {
|
|
this.removeNotification(this.notification, true)
|
|
} else {
|
|
this.resetPosition()
|
|
}
|
|
} else {
|
|
if (this.touchEndX > this.touchStartX) {
|
|
this.removeNotification(this.notification)
|
|
} else {
|
|
this.removeNotification(this.notification, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
movePopup = () => {
|
|
this.notification.style.transform = `translateX(${this.offset}px)`
|
|
}
|
|
|
|
resetPosition = () => {
|
|
this.notification.style.transform = `translateX(0)`
|
|
}
|
|
|
|
push = (messageBody, type, pinned) => {
|
|
let notification = document.createElement('div'),
|
|
composition = ``
|
|
notification.classList.add('notification')
|
|
if (pinned)
|
|
notification.classList.add('pinned')
|
|
if (type === 'error') {
|
|
composition += `
|
|
<svg class="notification-icon icon error-icon" viewBox="0 0 64 64">
|
|
<path d="M32,4.73a3.17,3.17,0,0,1,2.76,1.59l13.9,24.09L62.57,54.49a3.19,3.19,0,0,1-2.76,4.78H4.19a3.19,3.19,0,0,1-2.76-4.78L15.34,30.41,29.24,6.32A3.17,3.17,0,0,1,32,4.73m0-1a4.14,4.14,0,0,0-3.62,2.09L14.47,29.91.57,54a4.19,4.19,0,0,0,3.62,6.28H59.81A4.19,4.19,0,0,0,63.43,54L49.53,29.91,35.62,5.82A4.14,4.14,0,0,0,32,3.73Z"/>
|
|
<line x1="32" y1="24" x2="32" y2="36"/>
|
|
<line x1="32" y1="46" x2="32" y2="48"/>
|
|
</svg>`
|
|
} else if (type === 'success') {
|
|
composition += `
|
|
<svg class="notification-icon icon success-icon" viewBox="0 0 64 64">
|
|
<polyline points="0.35 31.82 21.45 52.98 63.65 10.66"/>
|
|
</svg>`
|
|
}
|
|
composition += `
|
|
<p>${messageBody}</p>
|
|
<svg class="icon close" viewBox="0 0 64 64">
|
|
<title>Close</title>
|
|
<line x1="64" y1="0" x2="0" y2="64"/>
|
|
<line x1="64" y1="64" x2="0" y2="0"/>
|
|
</svg>`
|
|
notification.innerHTML = composition
|
|
this.notificationPanel.prepend(notification)
|
|
if (window.innerWidth > 640) {
|
|
notification.animate([{
|
|
transform: `translateX(1rem)`,
|
|
opacity: '0'
|
|
},
|
|
{
|
|
transform: 'translateX(0)',
|
|
opacity: '1'
|
|
}
|
|
], this.animationOptions).onfinish = () => {
|
|
notification.setAttribute('style', `transform: none;`);
|
|
}
|
|
} else {
|
|
notification.setAttribute('style', `transform: translateY(0); opacity: 1`)
|
|
}
|
|
notification.addEventListener('touchstart', this.handleTouchStart)
|
|
notification.addEventListener('touchmove', this.handleTouchMove)
|
|
notification.addEventListener('touchend', this.handleTouchEnd)
|
|
}
|
|
|
|
removeNotification = (notification, toLeft) => {
|
|
if (!this.offset)
|
|
this.offset = 0;
|
|
|
|
if (toLeft)
|
|
notification.animate([{
|
|
transform: `translateX(${this.offset}px)`,
|
|
opacity: '1'
|
|
},
|
|
{
|
|
transform: `translateX(-100%)`,
|
|
opacity: '0'
|
|
}
|
|
], this.animationOptions).onfinish = () => {
|
|
notification.remove()
|
|
}
|
|
else {
|
|
notification.animate([{
|
|
transform: `translateX(${this.offset}px)`,
|
|
opacity: '1'
|
|
},
|
|
{
|
|
transform: `translateX(100%)`,
|
|
opacity: '0'
|
|
}
|
|
], this.animationOptions).onfinish = () => {
|
|
notification.remove()
|
|
}
|
|
}
|
|
}
|
|
|
|
clearAll = () => {
|
|
Array.from(this.notificationPanel.children).forEach(child => {
|
|
this.removeNotification(child)
|
|
})
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.notificationPanel = this.shadowRoot.querySelector('.notification-panel')
|
|
this.animationOptions = {
|
|
duration: 300,
|
|
fill: "forwards",
|
|
easing: "ease"
|
|
}
|
|
this.fontSize = Number(window.getComputedStyle(document.body).getPropertyValue('font-size').match(/\d+/)[0])
|
|
this.notification
|
|
this.offset
|
|
this.touchStartX = 0
|
|
this.touchEndX = 0
|
|
this.touchStartTime = 0
|
|
this.touchEndTime = 0
|
|
this.threshold = this.notificationPanel.getBoundingClientRect().width * 0.3
|
|
this.touchEndAnimataion;
|
|
|
|
this.notificationPanel.addEventListener('click', e => {
|
|
if (e.target.closest('.close'))(
|
|
this.removeNotification(e.target.closest('.notification'))
|
|
)
|
|
})
|
|
|
|
const observer = new MutationObserver(mutationList => {
|
|
mutationList.forEach(mutation => {
|
|
if (mutation.type === 'childList') {
|
|
if (mutation.addedNodes.length) {
|
|
if (!mutation.addedNodes[0].classList.contains('pinned'))
|
|
setTimeout(() => {
|
|
this.removeNotification(mutation.addedNodes[0])
|
|
}, 5000);
|
|
if (window.innerWidth > 640)
|
|
this.notificationPanel.style.padding = '1.5rem 0 3rem 1.5rem';
|
|
else
|
|
this.notificationPanel.style.padding = '1rem 1rem 2rem 1rem';
|
|
} else if (mutation.removedNodes.length && !this.notificationPanel.children.length) {
|
|
this.notificationPanel.style.padding = 0;
|
|
}
|
|
}
|
|
})
|
|
})
|
|
observer.observe(this.notificationPanel, {
|
|
attributes: true,
|
|
childList: true,
|
|
subtree: true
|
|
})
|
|
}
|
|
})
|
|
|
|
|
|
// tab-header
|
|
const smTabHeader = document.createElement('template')
|
|
smTabHeader.innerHTML = `
|
|
<style>
|
|
*{
|
|
padding: 0;
|
|
margin: 0;
|
|
-webkit-box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
}
|
|
:host{
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
--active-tab-color: rgba(var(--foreground-color), 1);
|
|
}
|
|
.tabs{
|
|
position: relative;
|
|
display: -ms-grid;
|
|
display: grid;
|
|
width: 100%;
|
|
}
|
|
.tab-header{
|
|
display: -ms-grid;
|
|
display: grid;
|
|
grid-auto-flow: column;
|
|
-webkit-box-pack: start;
|
|
-ms-flex-pack: start;
|
|
justify-content: flex-start;
|
|
gap: 1rem;
|
|
position: relative;
|
|
overflow: auto hidden;
|
|
max-width: 100%;
|
|
scrollbar-width: 0;
|
|
}
|
|
.indicator{
|
|
position: absolute;
|
|
left: 0;
|
|
bottom: 0;
|
|
height: 0.15rem;
|
|
border-radius: 1rem 1rem 0 0;
|
|
background: var(--accent-color);
|
|
-webkit-transition: width 0.3s, -webkit-transform 0.3s;
|
|
transition: width 0.3s, -webkit-transform 0.3s;
|
|
-o-transition: transform 0.3s, width 0.3s;
|
|
transition: transform 0.3s, width 0.3s;
|
|
transition: transform 0.3s, width 0.3s, -webkit-transform 0.3s;
|
|
pointer-events: none;
|
|
}
|
|
:host([variant="tab"]) .indicator{
|
|
height: 100%;
|
|
border-radius: 0.3rem;
|
|
}
|
|
:host(.round) .indicator{
|
|
border-radius: 3rem;
|
|
}
|
|
:host([variant="tab"]) .tab-header{
|
|
border-bottom: none;
|
|
}
|
|
.hide-completely{
|
|
display: none;
|
|
}
|
|
:host([variant="tab"]) .tab-header{
|
|
gap: 0.2rem;
|
|
display: -ms-inline-grid;
|
|
display: inline-grid;
|
|
justify-self: flex-start;
|
|
border-radius: 0.3rem;
|
|
}
|
|
:host([variant="tab"]) slot::slotted(.active){
|
|
color: var(--active-tab-color);
|
|
}
|
|
slot::slotted(.active){
|
|
color: var(--accent-color);
|
|
opacity: 1;
|
|
}
|
|
@media (hover: none){
|
|
.tab-header::-webkit-scrollbar-track {
|
|
-webkit-box-shadow: none !important;
|
|
background-color: transparent !important;
|
|
}
|
|
.tab-header::-webkit-scrollbar {
|
|
height: 0;
|
|
background-color: transparent;
|
|
}
|
|
}
|
|
</style>
|
|
<div part="tab-container" class="tabs">
|
|
<div part="tab-header" class="tab-header">
|
|
<slot></slot>
|
|
<div part="indicator" class="indicator"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
customElements.define('sm-tab-header', class extends HTMLElement {
|
|
constructor() {
|
|
super()
|
|
this.attachShadow({
|
|
mode: 'open'
|
|
}).append(smTabHeader.content.cloneNode(true))
|
|
|
|
this.indicator = this.shadowRoot.querySelector('.indicator');
|
|
this.tabSlot = this.shadowRoot.querySelector('slot');
|
|
this.tabHeader = this.shadowRoot.querySelector('.tab-header');
|
|
}
|
|
|
|
sendDetails(element) {
|
|
this.dispatchEvent(
|
|
new CustomEvent("switchtab", {
|
|
bubbles: true,
|
|
detail: {
|
|
target: this.target,
|
|
rank: parseInt(element.dataset.rank)
|
|
}
|
|
})
|
|
)
|
|
}
|
|
|
|
moveIndiactor(tabDimensions) {
|
|
//if(this.isTab)
|
|
this.indicator.setAttribute('style', `width: ${tabDimensions.width}px; transform: translateX(${tabDimensions.left - this.tabHeader.getBoundingClientRect().left + this.tabHeader.scrollLeft}px)`)
|
|
//else
|
|
//this.indicator.setAttribute('style', `width: calc(${tabDimensions.width}px - 1.6rem); transform: translateX(calc(${ tabDimensions.left - this.tabHeader.getBoundingClientRect().left + this.tabHeader.scrollLeft}px + 0.8rem)`)
|
|
}
|
|
|
|
connectedCallback() {
|
|
if (!this.hasAttribute('target') || this.getAttribute('target').value === '') return;
|
|
this.prevTab
|
|
this.allTabs
|
|
this.activeTab
|
|
this.isTab = false
|
|
this.target = this.getAttribute('target')
|
|
|
|
if (this.hasAttribute('variant') && this.getAttribute('variant') === 'tab') {
|
|
this.isTab = true
|
|
}
|
|
|
|
this.tabSlot.addEventListener('slotchange', () => {
|
|
this.tabSlot.assignedElements().forEach((tab, index) => {
|
|
tab.dataset.rank = index
|
|
})
|
|
})
|
|
this.allTabs = this.tabSlot.assignedElements();
|
|
|
|
this.tabSlot.addEventListener('click', e => {
|
|
if (e.target === this.prevTab || !e.target.closest('sm-tab'))
|
|
return
|
|
if (this.prevTab)
|
|
this.prevTab.classList.remove('active')
|
|
e.target.classList.add('active')
|
|
|
|
e.target.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'nearest',
|
|
inline: 'center'
|
|
})
|
|
this.moveIndiactor(e.target.getBoundingClientRect())
|
|
this.sendDetails(e.target)
|
|
this.prevTab = e.target;
|
|
this.activeTab = e.target;
|
|
})
|
|
let resizeObserver = new ResizeObserver(entries => {
|
|
entries.forEach((entry) => {
|
|
if (this.prevTab) {
|
|
let tabDimensions = this.activeTab.getBoundingClientRect();
|
|
this.moveIndiactor(tabDimensions)
|
|
}
|
|
})
|
|
})
|
|
resizeObserver.observe(this)
|
|
let observer = new IntersectionObserver((entries) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
this.indicator.style.transition = 'none'
|
|
if (this.activeTab) {
|
|
let tabDimensions = this.activeTab.getBoundingClientRect();
|
|
this.moveIndiactor(tabDimensions)
|
|
} else {
|
|
this.allTabs[0].classList.add('active')
|
|
let tabDimensions = this.allTabs[0].getBoundingClientRect();
|
|
this.moveIndiactor(tabDimensions)
|
|
this.sendDetails(this.allTabs[0])
|
|
this.prevTab = this.tabSlot.assignedElements()[0];
|
|
this.activeTab = this.prevTab;
|
|
}
|
|
}
|
|
})
|
|
}, {
|
|
threshold: 1.0
|
|
})
|
|
observer.observe(this)
|
|
}
|
|
})
|
|
|
|
// tab
|
|
const smTab = document.createElement('template')
|
|
smTab.innerHTML = `
|
|
<style>
|
|
*{
|
|
padding: 0;
|
|
margin: 0;
|
|
-webkit-box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
}
|
|
:host{
|
|
position: relative;
|
|
display: -webkit-inline-box;
|
|
display: -ms-inline-flexbox;
|
|
display: inline-flex;
|
|
z-index: 1;
|
|
}
|
|
.tab{
|
|
position: relative;
|
|
-webkit-user-select: none;
|
|
-moz-user-select: none;
|
|
-ms-user-select: none;
|
|
user-select: none;
|
|
-webkit-box-pack: center;
|
|
-ms-flex-pack: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
-webkit-tap-highlight-color: transparent;
|
|
white-space: nowrap;
|
|
padding: 0.4rem 0.8rem;
|
|
font-weight: 500;
|
|
word-spacing: 0.1rem;
|
|
text-align: center;
|
|
-webkit-transition: color 0.3s;
|
|
-o-transition: color 0.3s;
|
|
transition: color 0.3s;
|
|
text-transform: capitalize;
|
|
height: 100%;
|
|
}
|
|
@media (hover: hover){
|
|
:host(.active) .tab{
|
|
opacity: 1;
|
|
}
|
|
.tab{
|
|
opacity: 0.7
|
|
}
|
|
.tab:hover{
|
|
opacity: 1
|
|
}
|
|
}
|
|
</style>
|
|
<div part="tab" class="tab">
|
|
<slot></slot>
|
|
</div>
|
|
`;
|
|
|
|
customElements.define('sm-tab', class extends HTMLElement {
|
|
constructor() {
|
|
super()
|
|
this.shadow = this.attachShadow({
|
|
mode: 'open'
|
|
}).append(smTab.content.cloneNode(true))
|
|
}
|
|
})
|
|
|
|
// tab-panels
|
|
const smTabPanels = document.createElement('template')
|
|
smTabPanels.innerHTML = `
|
|
<style>
|
|
*{
|
|
padding: 0;
|
|
margin: 0;
|
|
-webkit-box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
}
|
|
.panel-container{
|
|
position: relative;
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden auto;
|
|
}
|
|
slot::slotted(.hide-completely){
|
|
display: none;
|
|
}
|
|
</style>
|
|
<div part="panel-container" class="panel-container">
|
|
<slot>Nothing to see here.</slot>
|
|
</div>
|
|
`;
|
|
|
|
customElements.define('sm-tab-panels', class extends HTMLElement {
|
|
constructor() {
|
|
super()
|
|
this.attachShadow({
|
|
mode: 'open'
|
|
}).append(smTabPanels.content.cloneNode(true))
|
|
this.panelSlot = this.shadowRoot.querySelector('slot');
|
|
}
|
|
connectedCallback() {
|
|
|
|
//animations
|
|
let flyInLeft = [{
|
|
opacity: 0,
|
|
transform: 'translateX(-1rem)'
|
|
},
|
|
{
|
|
opacity: 1,
|
|
transform: 'none'
|
|
}
|
|
],
|
|
flyInRight = [{
|
|
opacity: 0,
|
|
transform: 'translateX(1rem)'
|
|
},
|
|
{
|
|
opacity: 1,
|
|
transform: 'none'
|
|
}
|
|
],
|
|
flyOutLeft = [{
|
|
opacity: 1,
|
|
transform: 'none'
|
|
},
|
|
{
|
|
opacity: 0,
|
|
transform: 'translateX(-1rem)'
|
|
}
|
|
],
|
|
flyOutRight = [{
|
|
opacity: 1,
|
|
transform: 'none'
|
|
},
|
|
{
|
|
opacity: 0,
|
|
transform: 'translateX(1rem)'
|
|
}
|
|
],
|
|
animationOptions = {
|
|
duration: 300,
|
|
fill: 'forwards',
|
|
easing: 'ease'
|
|
}
|
|
this.prevPanel
|
|
this.allPanels
|
|
this.previousRank
|
|
|
|
this.panelSlot.addEventListener('slotchange', () => {
|
|
this.panelSlot.assignedElements().forEach((panel) => {
|
|
panel.classList.add('hide-completely')
|
|
})
|
|
})
|
|
this.allPanels = this.panelSlot.assignedElements()
|
|
this._targetBodyFlyRight = (targetBody) => {
|
|
targetBody.classList.remove('hide-completely')
|
|
targetBody.animate(flyInRight, animationOptions)
|
|
}
|
|
this._targetBodyFlyLeft = (targetBody) => {
|
|
targetBody.classList.remove('hide-completely')
|
|
targetBody.animate(flyInLeft, animationOptions)
|
|
}
|
|
document.addEventListener('switchtab', e => {
|
|
if (e.detail.target !== this.id)
|
|
return
|
|
|
|
if (this.prevPanel) {
|
|
let targetBody = this.allPanels[e.detail.rank],
|
|
currentBody = this.prevPanel;
|
|
if (this.previousRank < e.detail.rank) {
|
|
if (currentBody && !targetBody)
|
|
currentBody.animate(flyOutLeft, animationOptions).onfinish = () => {
|
|
currentBody.classList.add('hide-completely')
|
|
}
|
|
else if (targetBody && !currentBody) {
|
|
this._targetBodyFlyRight(targetBody)
|
|
} else if (currentBody && targetBody) {
|
|
currentBody.animate(flyOutLeft, animationOptions).onfinish = () => {
|
|
currentBody.classList.add('hide-completely')
|
|
this._targetBodyFlyRight(targetBody)
|
|
}
|
|
}
|
|
} else {
|
|
if (currentBody && !targetBody)
|
|
currentBody.animate(flyOutRight, animationOptions).onfinish = () => {
|
|
currentBody.classList.add('hide-completely')
|
|
}
|
|
else if (targetBody && !currentBody) {
|
|
this._targetBodyFlyLeft(targetBody)
|
|
} else if (currentBody && targetBody) {
|
|
currentBody.animate(flyOutRight, animationOptions).onfinish = () => {
|
|
currentBody.classList.add('hide-completely')
|
|
this._targetBodyFlyLeft(targetBody)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
this.allPanels[e.detail.rank].classList.remove('hide-completely')
|
|
}
|
|
this.previousRank = e.detail.rank
|
|
this.prevPanel = this.allPanels[e.detail.rank];
|
|
})
|
|
}
|
|
})
|
|
|
|
const fundBlockTemplate = document.createElement('template')
|
|
fundBlockTemplate.innerHTML = `
|
|
<style>
|
|
*{
|
|
padding: 0;
|
|
margin: 0;
|
|
box-sizing: border-box;
|
|
font-family: 'Inter', sans-serif;
|
|
}
|
|
ul{
|
|
list-style: none;
|
|
}
|
|
a:any-link,
|
|
button{
|
|
position: relative;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
background: none;
|
|
cursor: pointer;
|
|
outline: none;
|
|
color: inherit;
|
|
font-weight: 500;
|
|
font-size: 0.8rem;
|
|
border-radius: 0.3rem;
|
|
padding: 0.4rem 0.6rem;
|
|
align-self: flex-start;
|
|
text-decoration: none;
|
|
-webkit-tap-highlight-color: transparent;
|
|
border: 1px solid rgba(var(--text-color), 0.8);
|
|
}
|
|
a:any-link:focus-visible,
|
|
button:focus-visible{
|
|
outline: rgba(var(--text-color), 1) 0.1rem solid;
|
|
}
|
|
.flex{
|
|
display: flex;
|
|
}
|
|
.grid{
|
|
display: grid;
|
|
}
|
|
.gap-1{
|
|
gap: 1rem;
|
|
}
|
|
.gap-1-5{
|
|
gap: 1.5rem;
|
|
}
|
|
.flow-column{
|
|
grid-auto-flow: column;
|
|
}
|
|
.justify-start{
|
|
justify-content: flex-start;
|
|
}
|
|
.justify-end{
|
|
justify-content: flex-end;
|
|
}
|
|
.justify-right{
|
|
margin-left: auto;
|
|
}
|
|
.margin-bottom-0-5r{
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
.margin-bottom-1-5r{
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
.margin-bottom-3r{
|
|
margin-bottom: 3rem;
|
|
}
|
|
p {
|
|
font-size: 0.9rem;
|
|
max-width: 70ch;
|
|
line-height: 1.5;
|
|
color: rgba(var(--text-color), 0.8);
|
|
}
|
|
.hide-completely{
|
|
display: none !important;
|
|
}
|
|
.fund-block{
|
|
border-radius: 0.3rem;
|
|
padding: 1rem;
|
|
background-color: rgba(var(--foreground-color), 1);
|
|
box-shadow: 0 1rem 2rem -1rem rgba($color: #000000, $alpha: 0.16);
|
|
}
|
|
.fund-block__details {
|
|
gap: 2rem;
|
|
}
|
|
.fund-block__details > .grid:nth-of-type(1), .fund-block__details > .grid:nth-of-type(2) {
|
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
.tapout-container, .investment-container {
|
|
grid-column: 1/3;
|
|
}
|
|
.investment-container{
|
|
gap: 1.5rem;
|
|
justify-content: flex-start;
|
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
.tapout-list{
|
|
display: grid;
|
|
gap: 1.5rem;
|
|
overflow-x: auto;
|
|
grid-auto-flow: column;
|
|
justify-content: flex-start;
|
|
}
|
|
.tapout-list li{
|
|
min-width: 8rem;
|
|
}
|
|
.fund-link{
|
|
justify-self: flex-end;
|
|
}
|
|
.investor-group{
|
|
display: grid;
|
|
padding: 0.5rem;
|
|
margin-top: 1.8rem;
|
|
border-radius: 0.3rem;
|
|
box-shadow: 0 0 0 1px rgba(var(--text-color), 0.3);
|
|
}
|
|
.investor-group header{
|
|
margin-top: -1.5rem;
|
|
margin-bottom: 0.5rem;
|
|
justify-self: flex-end;
|
|
}
|
|
.investor-group > * {
|
|
background-color: rgba(var(--foreground-color), 1);
|
|
}
|
|
.investor-group h4{
|
|
padding: 0 0.5rem;
|
|
margin-left: -0.5rem;
|
|
}
|
|
|
|
.investor-group__list{
|
|
display: grid;
|
|
gap: 0.8rem;
|
|
grid-template-columns: minmax(0, 1fr);
|
|
}
|
|
.transaction-column{
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.label{
|
|
font-weight: 500;
|
|
font-size: 0.75rem;
|
|
margin-bottom: 0.2rem;
|
|
color: rgba(var(--text-color), 0.8);
|
|
}
|
|
.value{
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
overflow-wrap: break-word;
|
|
word-wrap: break-word;
|
|
-ms-word-break: break-all;
|
|
word-break: break-word;
|
|
-ms-hyphens: auto;
|
|
-moz-hyphens: auto;
|
|
-webkit-hyphens: auto;
|
|
hyphens: auto;
|
|
}
|
|
@media only screen and (max-width: 640px) {
|
|
.fund-link{
|
|
grid-column: 2/3;
|
|
}
|
|
}
|
|
@media only screen and (min-width: 640px) {
|
|
.fund-block{
|
|
padding: 1.5rem;
|
|
}
|
|
.fund-block__details{
|
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
.investor-group{
|
|
padding: 0.5rem 1rem;
|
|
}
|
|
.justify-self-end{
|
|
justify-self: flex-end;
|
|
text-align: right;
|
|
}
|
|
.investment-container{
|
|
grid-template-columns: auto auto 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
<section class="fund-block">
|
|
<div class="fund-block__details grid margin-bottom-3r">
|
|
<div class="grid flow-column gap-1">
|
|
<div class="grid">
|
|
<span class="label">Start date</span>
|
|
<span class="value start-date"></span>
|
|
</div>
|
|
<div class="grid">
|
|
<span class="label">End date</span>
|
|
<span class="value end-date"></span>
|
|
</div>
|
|
</div>
|
|
<div class="grid flow-column gap-1">
|
|
<div class="grid">
|
|
<span class="label">Initial BTC value</span>
|
|
<span class="value base-btc"></span>
|
|
</div>
|
|
<div class="grid">
|
|
<span class="label">Base USD rate</span>
|
|
<span class="value base-usd"></span>
|
|
</div>
|
|
</div>
|
|
<div class="tapout-container grid">
|
|
<span class="value margin-bottom-0-5r">Tapouts</span>
|
|
<ul class="tapout-list"></ul>
|
|
</div>
|
|
<div class="investment-container grid">
|
|
<div class="grid">
|
|
<span class="label">Total investment</span>
|
|
<span class="value total-investment"></span>
|
|
</div>
|
|
<div class="grid">
|
|
<span class="label">Total present value</span>
|
|
<span class="value net-value" style="color: var(--green)"></span>
|
|
</div>
|
|
<a class="fund-link" target="_blank">See fund on Blockchain</a>
|
|
</div>
|
|
</div>
|
|
<div class="grid">
|
|
<h4 class="margin-bottom-0-5r">Investors</h4>
|
|
<ul class="investors-group-list grid"></ul>
|
|
</div>
|
|
</section>
|
|
`
|
|
|
|
customElements.define('fund-block', class extends HTMLElement {
|
|
constructor() {
|
|
super()
|
|
this.shadow = this.attachShadow({
|
|
mode: 'open'
|
|
})
|
|
this.shadow.append(fundBlockTemplate.content.cloneNode(true))
|
|
this.totalInvestement = {}
|
|
this.totalNet = {}
|
|
}
|
|
|
|
get investors(){
|
|
return this.shadow.querySelectorAll('fund-investor')
|
|
}
|
|
|
|
get investorGroups(){
|
|
return this.shadow.querySelectorAll('.investor-group')
|
|
}
|
|
|
|
get investorGroupList(){
|
|
return this.shadow.querySelectorAll('.investor-group__list')
|
|
}
|
|
|
|
set data(obj){
|
|
const {
|
|
fundTxHref,
|
|
startDate,
|
|
endDate,
|
|
baseUsd,
|
|
baseBtc,
|
|
tapouts,
|
|
totalInvestment,
|
|
totalNet,
|
|
investorsFrag
|
|
} = obj
|
|
|
|
this.totalInvestment = totalInvestment
|
|
this.totalNet = totalNet
|
|
|
|
for (let item in this.totalInvestment){
|
|
this.totalInvestment[item] = parseFloat(this.totalInvestment[item])
|
|
}
|
|
|
|
for (let item in this.totalNet){
|
|
this.totalNet[item] = parseFloat(this.totalNet[item])
|
|
}
|
|
if(investorsFrag.childElementCount > 1)
|
|
this.shadow.querySelector('.fund-link').href = fundTxHref
|
|
else
|
|
this.shadow.querySelector('.fund-link').remove()
|
|
this.shadow.querySelector('.start-date').textContent = startDate
|
|
this.shadow.querySelector('.end-date').textContent = endDate
|
|
this.shadow.querySelector('.base-usd').textContent = `${baseUsd.toLocaleString(`en-US`, {style: 'currency', currency: 'INR'})}`
|
|
this.shadow.querySelector('.base-btc').textContent = `${baseBtc.toLocaleString(`en-US`, {style: 'currency', currency: 'USD'})}`
|
|
this.shadow.querySelector('.investors-group-list').append(investorsFrag)
|
|
|
|
this.toggleCurrency()
|
|
if(Object.keys(tapouts).length){
|
|
const tapoutsFrag = document.createDocumentFragment()
|
|
for(let tapout in tapouts){
|
|
const tapoutPoint = document.createElement('li')
|
|
tapoutPoint.classList.add('grid')
|
|
tapoutPoint.innerHTML = `
|
|
<span class="label">${tapout}</span>
|
|
<span class="value">${tapouts[tapout]}</span>
|
|
`
|
|
tapoutsFrag.append(tapoutPoint)
|
|
}
|
|
this.shadow.querySelector('.tapout-list').append(tapoutsFrag)
|
|
} else
|
|
this.shadow.querySelector('.tapout-container').remove()
|
|
}
|
|
|
|
toggleCurrency = () => {
|
|
this.shadow.querySelector('.total-investment').textContent = `${this.totalInvestment[preferredCurrency].toLocaleString(`en-${preferredCurrency.substring(0,2)}`, {style: 'currency', currency: preferredCurrency})}`
|
|
this.shadow.querySelector('.net-value').textContent = `${this.totalNet[preferredCurrency].toLocaleString(`en-${preferredCurrency.substring(0,2)}`, {style: 'currency', currency: preferredCurrency})}`
|
|
}
|
|
})
|
|
|
|
|
|
const render = {
|
|
investorInput(){
|
|
const investorInput = document.createElement('li')
|
|
investorInput.classList.add('investor-input', 'grid')
|
|
investorInput.innerHTML = `
|
|
<sm-input placeholder="FLO ID" class="outlined" animate></sm-input>
|
|
<sm-input placeholder="Amount(₹)" type="number" min=0 class="outlined" animate></sm-input>
|
|
<button class="remove-investor" title="Remove this investor">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/></svg>
|
|
</button>
|
|
`
|
|
return investorInput
|
|
},
|
|
fundPlaceholder(){
|
|
const fund_ph = document.createElement('li')
|
|
fund_ph.classList.add('fund-placeholder', 'grid')
|
|
fund_ph.innerHTML = `
|
|
<div class="flex-grid justify-start">
|
|
<div class="placeholder__block"></div>
|
|
<div class="placeholder__block"></div>
|
|
<div class="placeholder__block"></div>
|
|
<div class="placeholder__block"></div>
|
|
</div>
|
|
<div class="grid flow-column gap-1">
|
|
<div class="placeholder__block"></div>
|
|
<div class="placeholder__block"></div>
|
|
</div>
|
|
<div class="placeholder__block justify-self-end"></div>
|
|
`
|
|
return fund_ph
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const domRefs = {};
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
//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",
|
|
"",
|
|
true
|
|
);
|
|
window.addEventListener("offline", () => {
|
|
notify(
|
|
"There seems to be a problem connecting to the internet, Please check you internet connection.",
|
|
"error",
|
|
true,
|
|
true
|
|
);
|
|
});
|
|
window.addEventListener("online", () => {
|
|
getRef("notification_drawer").clearAll();
|
|
notify("We are back online.", "success");
|
|
});
|
|
|
|
const themeSwitcher = getRef("theme_switcher");
|
|
|
|
if (themeSwitcher) {
|
|
if (localStorage.theme === "dark") {
|
|
nightlight();
|
|
themeSwitcher.checked = true;
|
|
} else {
|
|
daylight();
|
|
themeSwitcher.checked = false;
|
|
}
|
|
|
|
function daylight() {
|
|
document.body.setAttribute("data-theme", "light");
|
|
}
|
|
|
|
function nightlight() {
|
|
document.body.setAttribute("data-theme", "dark");
|
|
}
|
|
themeSwitcher.addEventListener("change", function (e) {
|
|
if (this.checked) {
|
|
nightlight();
|
|
localStorage.setItem("theme", "dark");
|
|
} else {
|
|
daylight();
|
|
localStorage.setItem("theme", "light");
|
|
}
|
|
});
|
|
}
|
|
|
|
//Function for displaying toast notifications. pass in error for mode param if you want to show an error.
|
|
function notify(message, mode, pinned, sound) {
|
|
if (mode === "error") console.error(message);
|
|
else console.log(message);
|
|
getRef("notification_drawer").push(message, mode, pinned);
|
|
if (navigator.onLine && sound) {
|
|
getRef("notification_sound").currentTime = 0;
|
|
getRef("notification_sound").play();
|
|
}
|
|
}
|
|
|
|
window.addEventListener("load", () => {
|
|
document.addEventListener("pointerdown", (e) => {
|
|
if (e.target.closest("button, sm-button:not([disable]), .interact")) {
|
|
createRipple(e, e.target.closest("button, sm-button, .interact"));
|
|
}
|
|
else if(isDropdownOpen && !e.target.closest('.dropdown')){
|
|
changeDropdownState('profile_dropdown', 'hide')
|
|
}
|
|
});
|
|
});
|
|
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();
|
|
};
|
|
}
|
|
|
|
let timerId;
|
|
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);
|
|
}
|
|
|
|
|
|
let preferredCurrency
|
|
|
|
if(localStorage.getItem('preferred-currency')){
|
|
preferredCurrency = localStorage.getItem('preferred-currency')
|
|
document.querySelector(`sm-option[value="${localStorage.getItem('preferred-currency')}"]`).setAttribute('selected', '')
|
|
}
|
|
else{
|
|
preferredCurrency = 'inr'
|
|
localStorage.setItem('preferred-currency', 'inr')
|
|
getRef('currency_selector').value = 'INR'
|
|
document.querySelector(`sm-option[value="inr"]`).setAttribute('selected', '')
|
|
}
|
|
|
|
getRef('currency_selector').addEventListener('change', e => {
|
|
preferredCurrency = e.detail.value
|
|
localStorage.setItem('preferred-currency', e.detail.value)
|
|
document.querySelectorAll('fund-block').forEach(fund => fund.toggleCurrency())
|
|
})
|
|
|
|
let isDropdownOpen = false
|
|
|
|
function changeDropdownState(target, mode, trigger){
|
|
const options = {
|
|
duration: 300,
|
|
easing: 'ease',
|
|
fill: 'both',
|
|
}
|
|
if(mode === 'show'){
|
|
showDropdown(target, options, trigger)
|
|
}
|
|
else if(mode === 'toggle'){
|
|
if(isDropdownOpen){
|
|
hideDropdown(target, options)
|
|
}
|
|
else{
|
|
showDropdown(target, options, trigger)
|
|
}
|
|
}
|
|
else if (mode === 'hide'){
|
|
hideDropdown(target, options)
|
|
}
|
|
}
|
|
|
|
function showDropdown(target, options, trigger){
|
|
if(isDropdownOpen) return
|
|
if(trigger){
|
|
const triggerDimensions = trigger.getBoundingClientRect()
|
|
getRef(target).setAttribute('style', `top: ${triggerDimensions.top + triggerDimensions.height + document.documentElement.scrollTop}px; right: calc(${window.innerWidth - triggerDimensions.right}px - 1.5rem)`)
|
|
}
|
|
getRef(target).classList.remove('hide-completely')
|
|
getRef(target).animate([
|
|
{transform: 'translateY(-1rem)', opacity: 0},
|
|
{transform: 'translateY(0)', opacity: 1},
|
|
], options)
|
|
.onfinish = () => {
|
|
isDropdownOpen = true
|
|
}
|
|
}
|
|
|
|
function hideDropdown(target, options){
|
|
if(!isDropdownOpen) return
|
|
getRef(target).animate([
|
|
{transform: 'translateY(0)', opacity: 1},
|
|
{transform: 'translateY(-1rem)', opacity: 0},
|
|
], options)
|
|
.onfinish = () => {
|
|
isDropdownOpen = false
|
|
getRef(target).classList.add('hide-completely')
|
|
}
|
|
}
|
|
|
|
function showPage(target){
|
|
document.querySelector('.page:not(.hide-completely)')?.classList.add('hide-completely')
|
|
getRef(target).classList.remove('hide-completely')
|
|
if(target === 'home_page'){
|
|
clearAddedInvestors()
|
|
getRef("create_fund_form").reset()
|
|
if(window.location.hash !== ''){
|
|
getRef(`${window.location.hash.split('#').pop()}`).scrollIntoView({behavior: 'smooth', block: 'start'})
|
|
}
|
|
}
|
|
if(target !== 'confirm_term_page'){
|
|
getRef('get_term_private_key').value = ''
|
|
getRef('get_fund_private_key').value = ''
|
|
}
|
|
if(target === 'loading_page' || target === 'error_page'){
|
|
getRef('main_header').classList.add('hide-completely')
|
|
}
|
|
else{
|
|
getRef('main_header').classList.remove('hide-completely')
|
|
}
|
|
}
|
|
|
|
getRef('search_investor').addEventListener('input', e => {
|
|
throttle(() => {
|
|
document.querySelectorAll('fund-block').forEach(block => {
|
|
block.investors.forEach( child => {
|
|
if(child.dataset.floId.toLowerCase().includes(getRef('search_investor').value.trim().toLowerCase())){
|
|
child.classList.remove('hide-completely')
|
|
}
|
|
else{
|
|
child.classList.add('hide-completely')
|
|
}
|
|
})
|
|
block.investorGroupList.forEach(group => {
|
|
if(Array.from(group.children).every(elem => elem.classList.contains('hide-completely'))){
|
|
group.parentNode.classList.add('hide-completely')
|
|
}
|
|
else{
|
|
group.parentNode.classList.remove('hide-completely')
|
|
}
|
|
})
|
|
if(Array.from(block.investorGroups).every(elem => elem.classList.contains('hide-completely'))){
|
|
block.classList.add('hide-completely')
|
|
}
|
|
else{
|
|
block.classList.remove('hide-completely')
|
|
}
|
|
if(Array.from(getRef('fund_list').children).every(elem => elem.classList.contains('hide-completely'))){
|
|
getRef('fund_list__empty-state').classList.remove('hide-completely')
|
|
}
|
|
else{
|
|
getRef('fund_list__empty-state').classList.add('hide-completely')
|
|
}
|
|
})
|
|
}, 100)
|
|
})
|
|
|
|
|
|
getRef('investors_input_list').addEventListener('click', e => {
|
|
if(e.target.closest('.remove-investor')){
|
|
e.target.closest('.investor-input').remove()
|
|
}
|
|
})
|
|
|
|
function renderInvestor(){
|
|
getRef('investors_input_list').append(render.investorInput())
|
|
getRef('investors_input_list').lastElementChild.scrollIntoView()
|
|
getRef('investors_input_list').lastElementChild.children[0].focusIn()
|
|
}
|
|
|
|
function clearAddedInvestors(){
|
|
getRef('investors_input_list').innerHTML = ``
|
|
getRef('investors_input_list').append(render.investorInput())
|
|
}
|
|
|
|
function getAddedInvestors(){
|
|
const allInvestorsInput = getRef('investors_input_list').querySelectorAll('.investor-input')
|
|
const addedInvestors = []
|
|
allInvestorsInput.forEach(investor => {
|
|
const floId = investor.children[0].value.trim()
|
|
const amount = investor.children[1].value.trim()
|
|
addedInvestors.push([floId, amount])
|
|
})
|
|
return addedInvestors
|
|
}
|
|
|
|
getRef('fund_creation_toggle').addEventListener('change', e => {
|
|
if(e.detail.checked){
|
|
getRef('fund_details_form').classList.add('hide-completely')
|
|
getRef('fund_details_form').querySelectorAll('input').forEach(input => input.disabled = true)
|
|
getRef('create_fund_button').classList.add('hide-completely')
|
|
getRef('add_investors_button').classList.remove('hide-completely')
|
|
getRef('fund_selector_container').classList.remove('hide-completely')
|
|
}
|
|
else{
|
|
getRef('fund_details_form').classList.remove('hide-completely')
|
|
getRef('create_fund_button').classList.remove('hide-completely')
|
|
getRef('fund_details_form').querySelectorAll('input').forEach(input => input.disabled = false)
|
|
getRef('add_investors_button').classList.add('hide-completely')
|
|
getRef('fund_selector_container').classList.add('hide-completely')
|
|
}
|
|
})
|
|
|
|
getRef('tapout_toggle').addEventListener('change', e => {
|
|
if(!e.detail.checked){
|
|
getRef('tapout_container').classList.add('hide-completely')
|
|
getRef('tapout_container').querySelectorAll('input').forEach(input => input.disabled = true)
|
|
}
|
|
else{
|
|
getRef('tapout_container').classList.remove('hide-completely')
|
|
getRef('tapout_container').querySelectorAll('input').forEach(input => input.disabled = false)
|
|
}
|
|
})
|
|
/* function renderfundPlaceholder(){
|
|
getRef('fund_list').innerHTML = ``
|
|
const frag = document.createDocumentFragment()
|
|
for (let index = 0; index < 4; index++) {
|
|
frag.append(render.fundPlaceholder())
|
|
}
|
|
getRef('fund_list').append(frag)
|
|
} */
|
|
|
|
/* getRef('term_selector').addEventListener('change', e => {
|
|
const floID = e.detail.value
|
|
getRef('fund_selector').querySelectorAll('.fund-option').forEach(option => option.classList.add('hide-completely'))
|
|
getRef('fund_selector').querySelector(`.fund-option[data-flo-id="${floID}"]`)?.classList.remove('hide-completely')
|
|
}) */
|
|
|
|
getRef('refresh_button').addEventListener("click", refresh);
|
|
|
|
var USD_current, BTC_current;
|
|
function refresh(showLoader = true) {
|
|
if (showLoader)
|
|
showPage('loading_page')
|
|
getCurrentRates().then(async (rates) => {
|
|
USD_current = rates.USD_INR;
|
|
BTC_current = rates.BTC_USD;
|
|
console.log(`USD rate: ${USD_current} INR\nBTC rate: ${BTC_current} USD`);
|
|
getRef("usd-rate").textContent = `USD: ₹${rates.USD_INR.toFixed(2)}`;
|
|
getRef("btc-usd-rate").textContent = `BTC: ${parseFloat(rates.BTC_USD.toFixed(2)).toLocaleString(`en-US`, { style: 'currency', currency: 'USD' })}`;
|
|
getRef('fund_list').innerHTML = '';
|
|
getRef('fund_selector').innerHTML = ''
|
|
//getRef('term_selector').innerHTML = ''
|
|
refreshBlockchainData().then(funds => {
|
|
renderFunds(funds)
|
|
showPage('home_page')
|
|
}).catch(error => {
|
|
console.error(error)
|
|
showPage('error_page')
|
|
})
|
|
}).catch(error => console.error(error))
|
|
}
|
|
|
|
function renderFunds(funds) {
|
|
if (!Object.keys(funds).length)
|
|
return;
|
|
const removeElementIfExist = id => {
|
|
let existing = document.getElementById(id);
|
|
if (existing)
|
|
existing.remove();
|
|
}
|
|
|
|
const fundsFrag = document.createDocumentFragment()
|
|
let sortArray = [];
|
|
|
|
for (let k in funds) {
|
|
let f = bobsFund.parse(funds[k].map(a => a.data));
|
|
console.info(f);
|
|
let startDate = new Date(f.start_date).getTime()
|
|
const tapouts = {}
|
|
if (f.tapoutInterval)
|
|
f.tapoutInterval.forEach((i, k) => {
|
|
let ts = bobsFund.dateAdder(startDate, i),
|
|
te = bobsFund.dateAdder(ts, f.topoutWindow);
|
|
tapouts[`Tapout ${k + 1}`] = `${bobsFund.dateFormat(ts)} to ${bobsFund.dateFormat(te)}`
|
|
})
|
|
const fundObj = {
|
|
//termTxHref: `${floBlockchainAPI.current_server}tx/${term.txid}`,
|
|
fundTxHref: `${floBlockchainAPI.current_server}tx/${funds[k][0].txid}`,
|
|
startDate: bobsFund.dateFormat(f.start_date),
|
|
endDate: bobsFund.dateFormat(bobsFund.dateAdder(startDate, f.duration)),
|
|
baseUsd: f.USD_base,
|
|
baseBtc: f.BTC_base,
|
|
tapouts,
|
|
}
|
|
|
|
// Creating fund selection options
|
|
const smOption = document.createElement('sm-option')
|
|
smOption.innerHTML = `
|
|
<div class="grid gap-0-5">
|
|
<span>Start date: ${fundObj.startDate}</span>
|
|
<span>Base BTC: ${parseFloat(fundObj.baseBtc).toLocaleString(`en-US`, {style: 'currency', currency: 'USD'})} | Base USD: ₹${fundObj.baseUsd}</span>
|
|
</div>
|
|
`
|
|
smOption.setAttribute('value', funds[k][0].txid)
|
|
fundsFrag.append(smOption)
|
|
|
|
const investorsFrag = document.createDocumentFragment()
|
|
let total_invested = total_net = 0;
|
|
for(let investor in f.investments){
|
|
const investorGroup = document.createElement('li')
|
|
investorGroup.classList.add('investor-group')
|
|
investorGroup.innerHTML = `
|
|
<header class="flex align-center">
|
|
<a class="tx-link justify-right" href="${floBlockchainAPI.current_server}tx/${funds[k][f.investments[investor].i].txid}" target="_blank">See transaction on Blockchain</a>
|
|
</header>
|
|
<ul class="investor-group__list"></ul>
|
|
`;
|
|
|
|
let amount = f.investments[investor].amount,
|
|
netVal = bobsFund.calcNetValue(f.BTC_base, BTC_current, f.USD_base, USD_current, amount, f.fee);
|
|
console.info(investor, amount, netVal);
|
|
const obj = {
|
|
floId: investor,
|
|
amountInvested: {
|
|
inr: amount.toFixed(2),
|
|
usd: (amount / f.USD_base).toFixed(2),
|
|
},
|
|
netValue: {
|
|
inr: netVal.toFixed(2),
|
|
usd: (netVal / USD_current).toFixed(2),
|
|
}
|
|
}
|
|
total_invested += amount;
|
|
total_net += netVal;
|
|
const investorCard = document.createElement('fund-investor')
|
|
investorCard.data = obj
|
|
if(f.investments[investor].closed){
|
|
/* TODO: UI: render closing data
|
|
if closed, netVal shoud not be displayed
|
|
f.investments[investor].closed -> Object {
|
|
BTC_net: BTC value at closing date
|
|
endDate: closing date
|
|
amountFinal: final amount (withdrawn)
|
|
payment_refRef: if amount is withdrawn via token system, txid of the token transfer
|
|
USD_net: USD value at closing date
|
|
refSign: signature of the inverstor for closing (hidden)
|
|
i: index of the txid (of closing tx) ie, funds[k][i].txid
|
|
}
|
|
*/
|
|
}
|
|
investorGroup.querySelector('.investor-group__list').append(investorCard)
|
|
investorsFrag.append(investorGroup)
|
|
}
|
|
fundObj.totalInvestment = {
|
|
inr: total_invested.toFixed(2),
|
|
usd: (total_invested / f.USD_base).toFixed(2)
|
|
}
|
|
fundObj.totalNet = {
|
|
inr: total_net.toFixed(2),
|
|
usd: (total_net / USD_current).toFixed(2)
|
|
}
|
|
fundObj.investorsFrag = investorsFrag
|
|
const fundBlock = document.createElement('fund-block')
|
|
fundBlock.data = fundObj
|
|
removeElementIfExist(k);
|
|
fundBlock.id = k;
|
|
let i = sortArray.length - 1;
|
|
while (i >= 0 && sortArray[i] < startDate) { //[9, 7, 5, 3]
|
|
sortArray[i + 1] = sortArray[i]
|
|
i--;
|
|
}
|
|
sortArray[i + 1] = startDate;
|
|
let list = getRef('fund_list')
|
|
list.insertBefore(fundBlock, list.childNodes[i + 1]);
|
|
}
|
|
|
|
//const fundGroup = document.createElement('section')
|
|
//fundGroup.dataset.floId = term.floID
|
|
//fundGroup.classList.add('fund-option')
|
|
//fundGroup.append(fundsFrag)
|
|
getRef('fund_selector').append(fundsFrag)
|
|
}
|
|
|
|
/* getRef("create_term_form").addEventListener("submit", evt => {
|
|
evt.preventDefault();
|
|
let f = evt.target;
|
|
let tap_it = getRef("tap_it").value
|
|
let tapoutInterval = f["tap_iv"].value.split(",").map(v => v.trim() + " " + tap_it);
|
|
let termStr = createTermString(f["floid"].value, f["max_pv"].value + " " + getRef("max_pt").value, f["tap_wv"].value + " " + getRef("tap_wt").value, tapoutInterval)
|
|
console.log(termStr)
|
|
|
|
getRef('term_details').innerHTML = termStr.replace(/\|/g, "<br>")
|
|
getRef('term_admin_id').innerHTML = `Enter Private key of adminID <h5 class="weight-400">${floGlobals.adminID}</h5>`
|
|
showPage('confirm_term_page')
|
|
getRef('create_term_button').onclick = () => {
|
|
const privKey = getRef('get_term_private_key').value
|
|
if (!floCrypto.verifyPrivKey(privKey, floGlobals.adminID)) {
|
|
notify("Access Denied! incorrect private key", 'error');
|
|
return
|
|
}
|
|
floBlockchainAPI.writeData(floGlobals.adminID, termStr, privKey, f["floid"].value).then(result => {
|
|
console.log(result);
|
|
showPage('admin_page')
|
|
notify("Term added in blockchain", 'success');
|
|
getRef("create_term_form").reset()
|
|
refresh(false)
|
|
}).catch(error => console.error(error))
|
|
}
|
|
}) */
|
|
|
|
getRef("create_fund_form").addEventListener("submit", evt => {
|
|
evt.preventDefault(); //tapout_toggle
|
|
let f, fStr, duration, tapoutWindow, tapoutInterval, investments;
|
|
f = evt.target
|
|
duration = f["max_pv"].value + " " + getRef("max_pt").value
|
|
if (!getRef("tapout_toggle").checked)
|
|
tapoutWindow = tapoutInterval = null
|
|
else {
|
|
let tap_it = getRef("tap_it").value
|
|
tapoutInterval = f["tap_iv"].value.split(",").map(v => v.trim() + " " + tap_it)
|
|
tapoutWindow = f["tap_wv"].value + " " + getRef("tap_wt").value
|
|
}
|
|
investments = getAddedInvestors()
|
|
|
|
let createMod = !getRef("fund_creation_toggle").checked;
|
|
console.info(investments)
|
|
if (createMod) //create new fund
|
|
fStr = bobsFund.stringify.main(f["btc_base"].value, f["usd_rate"].value, f["start_date"].value, duration, investments, f["fee"].value, tapoutWindow, tapoutInterval);
|
|
else //add investments to existing fund
|
|
fStr = bobsFund.stringify.continue(getRef("fund_selector").value, investments);
|
|
|
|
console.log(fStr);
|
|
if (fStr.length >= 1040) {
|
|
console.error("flo data length is too long, Please reduce it and try again");
|
|
notify("floData is too large! Please reduce it and try again.", 'error');
|
|
return
|
|
}
|
|
getRef('fund_details').innerHTML = fStr.replace(/\|/g, "<br>")
|
|
getRef('fund_admin_id').innerHTML = `Enter Private key of admin ID <h5 class="weight-400">${floGlobals.adminID}</h5>`
|
|
showPage('confirm_fund_page')
|
|
|
|
getRef('confirm_fund_button').onclick = () => {
|
|
const privKey = getRef('get_fund_private_key').value
|
|
console.log(privKey)
|
|
if (!floCrypto.verifyPrivKey(privKey, floGlobals.adminID)) {
|
|
notify("Access Denied! incorrect private key", 'error');
|
|
return
|
|
}
|
|
floBlockchainAPI.writeData(floGlobals.adminID, fStr, privKey, floGlobals.adminID).then(result => {
|
|
console.log(result);
|
|
showPage('admin_page')
|
|
notify(createMod ? "New Fund created" : "Invesments added to fund", 'success');
|
|
getRef("create_fund_form").reset()
|
|
clearAddedInvestors()
|
|
refresh(false)
|
|
}).catch(error => console.error(error))
|
|
}
|
|
})
|
|
|
|
</script>
|
|
|
|
</body>
|
|
</html> |