diff --git a/assets/icons.json b/assets/icons.json index 8812e206..4ea1806e 100644 --- a/assets/icons.json +++ b/assets/icons.json @@ -197,8 +197,13 @@ "l2beat": "M3.50521 3.54966C6.19251 1.25356 9.65297 1.67202 11.9929 3.71691C14.3301 1.67124 17.7537 1.28246 20.4709 3.54089C23.4445 6.01238 23.8016 10.2274 21.5118 13.1739C20.7167 14.197 19.2374 15.6826 17.7545 17.0967C16.255 18.5267 14.6963 19.9356 13.7042 20.8209L13.6822 20.8405C13.5197 20.9856 13.3571 21.1307 13.2072 21.2444C13.0387 21.3721 12.8318 21.5036 12.5679 21.5828C12.1958 21.6944 11.7907 21.6944 11.4186 21.5828C11.1547 21.5036 10.9478 21.3721 10.7793 21.2444C10.6294 21.1307 10.4669 20.9857 10.3043 20.8405L10.2823 20.8209C9.29019 19.9356 7.73154 18.5267 6.23203 17.0967C4.74915 15.6826 3.26977 14.197 2.47471 13.1739C0.176093 10.216 0.598423 6.03328 3.50521 3.54966ZM11.9488 8.18372C11.8209 7.80002 11.4744 7.53114 11.071 7.50247C10.6675 7.4738 10.2866 7.69098 10.1057 8.05274L8.88209 10.4999H8.50012C7.94784 10.4999 7.50012 10.9476 7.50012 11.4999C7.50012 12.0522 7.94784 12.4999 8.50012 12.4999H9.50012C9.87889 12.4999 10.2251 12.2859 10.3945 11.9471L10.8149 11.1065L12.0514 14.8161C12.1793 15.1998 12.5258 15.4687 12.9292 15.4974C13.3327 15.5261 13.7136 15.3089 13.8945 14.9471L15.1181 12.4999H15.5001C16.0524 12.4999 16.5001 12.0522 16.5001 11.4999C16.5001 10.9476 16.0524 10.4999 15.5001 10.4999H14.5001C14.1213 10.4999 13.7751 10.7139 13.6057 11.0527L13.1853 11.8934L11.9488 8.18372Z", "blockscout": "M 10.078125 3.554688 C 10.078125 3.066406 9.6875 2.667969 9.207031 2.667969 L 7.148438 2.667969 C 6.667969 2.667969 6.277344 3.066406 6.277344 3.554688 L 6.277344 5.660156 C 6.277344 6.152344 5.890625 6.550781 5.410156 6.550781 L 3.476562 6.550781 C 2.996094 6.550781 2.609375 6.949219 2.609375 7.441406 L 2.609375 20.421875 C 2.609375 20.914062 2.996094 21.3125 3.476562 21.3125 L 5.539062 21.3125 C 6.019531 21.3125 6.410156 20.914062 6.410156 20.421875 L 6.410156 7.441406 C 6.410156 6.949219 6.796875 6.550781 7.277344 6.550781 L 9.207031 6.550781 C 9.6875 6.550781 10.078125 6.152344 10.078125 5.660156 Z M 17.679688 3.554688 C 17.679688 3.066406 17.289062 2.667969 16.808594 2.667969 L 14.746094 2.667969 C 14.265625 2.667969 13.878906 3.066406 13.878906 3.554688 L 13.878906 5.660156 C 13.878906 6.152344 14.265625 6.550781 14.746094 6.550781 L 16.679688 6.550781 C 17.160156 6.550781 17.546875 6.949219 17.546875 7.441406 L 17.546875 20.421875 C 17.546875 20.914062 17.9375 21.3125 18.417969 21.3125 L 20.476562 21.3125 C 20.957031 21.3125 21.347656 20.914062 21.347656 20.421875 L 21.347656 7.441406 C 21.347656 6.949219 20.957031 6.550781 20.476562 6.550781 L 18.546875 6.550781 C 18.066406 6.550781 17.679688 6.152344 17.679688 5.660156 Z M 13.878906 10.992188 C 13.878906 10.5 13.488281 10.101562 13.007812 10.101562 L 10.949219 10.101562 C 10.46875 10.101562 10.078125 10.5 10.078125 10.992188 L 10.078125 16.761719 C 10.078125 17.25 10.46875 17.648438 10.949219 17.648438 L 13.007812 17.648438 C 13.488281 17.648438 13.878906 17.25 13.878906 16.761719 Z M 13.878906 10.992188", "lama": "M16.361 10.26a.9.9 0 0 0-.558.47l-.072.148l.001.207c0 .193.004.217.059.353c.076.193.152.312.291.448c.24.238.51.3.872.205a.86.86 0 0 0 .517-.436a.75.75 0 0 0 .08-.498c-.064-.453-.33-.782-.724-.897a1.1 1.1 0 0 0-.466 0m-9.203.005c-.305.096-.533.32-.65.639a1.2 1.2 0 0 0-.06.52c.057.309.31.59.598.667c.362.095.632.033.872-.205c.14-.136.215-.255.291-.448c.055-.136.059-.16.059-.353l.001-.207l-.072-.148a.9.9 0 0 0-.565-.472a1 1 0 0 0-.474.007m4.184 2c-.131.071-.223.25-.195.383c.031.143.157.288.353.407c.105.063.112.072.117.136c.004.038-.01.146-.029.243c-.02.094-.036.194-.036.222c.002.074.07.195.143.253c.064.052.076.054.255.059c.164.005.198.001.264-.03c.169-.082.212-.234.15-.525c-.052-.243-.042-.28.087-.355c.137-.08.281-.219.324-.314a.365.365 0 0 0-.175-.48a.4.4 0 0 0-.181-.033c-.126 0-.207.03-.355.124l-.085.053l-.053-.032c-.219-.13-.259-.145-.391-.143a.4.4 0 0 0-.193.032m.39-2.195c-.373.036-.475.05-.654.086a4.5 4.5 0 0 0-.951.328c-.94.46-1.589 1.226-1.787 2.114c-.04.176-.045.234-.045.53c0 .294.005.357.043.524c.264 1.16 1.332 2.017 2.714 2.173c.3.033 1.596.033 1.896 0c1.11-.125 2.064-.727 2.493-1.571c.114-.226.169-.372.22-.602c.039-.167.044-.23.044-.523c0-.297-.005-.355-.045-.531c-.288-1.29-1.539-2.304-3.072-2.497a7 7 0 0 0-.855-.031zm.645.937a3.3 3.3 0 0 1 1.44.514c.223.148.537.458.671.662c.166.251.26.508.303.82c.02.143.01.251-.043.482c-.08.345-.332.705-.672.957a3 3 0 0 1-.689.348c-.382.122-.632.144-1.525.138c-.582-.006-.686-.01-.853-.042q-.856-.16-1.35-.68c-.264-.28-.385-.535-.45-.946c-.03-.192.025-.509.137-.776c.136-.326.488-.73.836-.963c.403-.269.934-.46 1.422-.512c.187-.02.586-.02.773-.002m-5.503-11a1.65 1.65 0 0 0-.683.298C5.617.74 5.173 1.666 4.985 2.819c-.07.436-.119 1.04-.119 1.503c0 .544.064 1.24.155 1.721c.02.107.031.202.023.208l-.187.152a5.3 5.3 0 0 0-.949 1.02a5.5 5.5 0 0 0-.94 2.339a6.6 6.6 0 0 0-.023 1.357c.091.78.325 1.438.727 2.04l.13.195l-.037.064c-.269.452-.498 1.105-.605 1.732c-.084.496-.095.629-.095 1.294c0 .67.009.803.088 1.266c.095.555.288 1.143.503 1.534c.071.128.243.393.264.407c.007.003-.014.067-.046.141a7.4 7.4 0 0 0-.548 1.873a5 5 0 0 0-.071.991c0 .56.031.832.148 1.279L3.42 24h1.478l-.05-.091c-.297-.552-.325-1.575-.068-2.597c.117-.472.25-.819.498-1.296l.148-.29v-.177c0-.165-.003-.184-.057-.293a.9.9 0 0 0-.194-.25a1.7 1.7 0 0 1-.385-.543c-.424-.92-.506-2.286-.208-3.451c.124-.486.329-.918.544-1.154a.8.8 0 0 0 .223-.531c0-.195-.07-.355-.224-.522a3.14 3.14 0 0 1-.817-1.729c-.14-.96.114-2.005.69-2.834c.563-.814 1.353-1.336 2.237-1.475c.199-.033.57-.028.776.01c.226.04.367.028.512-.041c.179-.085.268-.19.374-.431c.093-.215.165-.333.36-.576c.234-.29.46-.489.822-.729c.413-.27.884-.467 1.352-.561c.17-.035.25-.04.569-.04s.398.005.569.04a4.07 4.07 0 0 1 1.914.997c.117.109.398.457.488.602c.034.057.095.177.132.267c.105.241.195.346.374.43c.14.068.286.082.503.045c.343-.058.607-.053.943.016c1.144.23 2.14 1.173 2.581 2.437c.385 1.108.276 2.267-.296 3.153c-.097.15-.193.27-.333.419c-.301.322-.301.722-.001 1.053c.493.539.801 1.866.708 3.036c-.062.772-.26 1.463-.533 1.854a2 2 0 0 1-.224.258a.9.9 0 0 0-.194.25c-.054.109-.057.128-.057.293v.178l.148.29c.248.476.38.823.498 1.295c.253 1.008.231 2.01-.059 2.581a1 1 0 0 0-.044.098c0 .006.329.009.732.009h.73l.02-.074l.036-.134c.019-.076.057-.3.088-.516a9 9 0 0 0 0-1.258c-.11-.875-.295-1.57-.597-2.226c-.032-.074-.053-.138-.046-.141a1.4 1.4 0 0 0 .108-.152c.376-.569.607-1.284.724-2.228c.031-.26.031-1.378 0-1.628c-.083-.645-.182-1.082-.348-1.525a6 6 0 0 0-.329-.7l-.038-.064l.131-.194c.402-.604.636-1.262.727-2.04a6.6 6.6 0 0 0-.024-1.358a5.5 5.5 0 0 0-.939-2.339a5.3 5.3 0 0 0-.95-1.02l-.186-.152a.7.7 0 0 1 .023-.208c.208-1.087.201-2.443-.017-3.503c-.19-.924-.535-1.658-.98-2.082c-.354-.338-.716-.482-1.15-.455c-.996.059-1.8 1.205-2.116 3.01a7 7 0 0 0-.097.726c0 .036-.007.066-.015.066a1 1 0 0 1-.149-.078A4.86 4.86 0 0 0 12 3.03c-.832 0-1.687.243-2.456.698a1 1 0 0 1-.148.078c-.008 0-.015-.03-.015-.066a7 7 0 0 0-.097-.725C8.997 1.392 8.337.319 7.46.048a2 2 0 0 0-.585-.041Zm.293 1.402c.248.197.523.759.682 1.388c.03.113.06.244.069.292c.007.047.026.152.041.233c.067.365.098.76.102 1.24l.002.475l-.12.175l-.118.178h-.278c-.324 0-.646.041-.954.124l-.238.06c-.033.007-.038-.003-.057-.144a8.4 8.4 0 0 1 .016-2.323c.124-.788.413-1.501.696-1.711c.067-.05.079-.049.157.013m9.825-.012c.17.126.358.46.498.888c.28.854.36 2.028.212 3.145c-.019.14-.024.151-.057.144l-.238-.06a3.7 3.7 0 0 0-.954-.124h-.278l-.119-.178l-.119-.175l.002-.474c.004-.669.066-1.19.214-1.772c.157-.623.434-1.185.68-1.382c.078-.062.09-.063.159-.012", + "celestials": "M 8 24 C 7.660156 24 7.382812 23.730469 7.382812 23.398438 L 7.382812 22.199219 C 7.382812 21.867188 7.660156 21.601562 8 21.601562 L 12.308594 21.601562 L 12.308594 23.398438 C 12.308594 23.730469 12.03125 24 11.691406 24 Z M 12.308594 21.601562 L 12.308594 19.199219 L 8 19.199219 C 7.660156 19.199219 7.382812 18.929688 7.382812 18.601562 L 7.382812 16.800781 L 5.539062 16.800781 C 5.199219 16.800781 4.921875 16.53125 4.921875 16.199219 L 4.921875 14.398438 L 3.078125 14.398438 C 2.738281 14.398438 2.460938 14.132812 2.460938 13.800781 L 2.460938 12 L 0.617188 12 C 0.273438 12 0 11.730469 0 11.398438 L 0 7.800781 C 0 7.46875 0.273438 7.199219 0.617188 7.199219 L 2.460938 7.199219 L 2.460938 5.398438 C 2.460938 5.070312 2.738281 4.800781 3.078125 4.800781 L 4.921875 4.800781 L 4.921875 3 C 4.921875 2.667969 5.199219 2.398438 5.539062 2.398438 L 7.382812 2.398438 L 7.382812 0.601562 C 7.382812 0.269531 7.660156 0 8 0 L 11.691406 0 C 12.03125 0 12.308594 0.269531 12.308594 0.601562 L 12.308594 1.800781 C 12.308594 2.132812 12.03125 2.398438 11.691406 2.398438 L 7.382812 2.398438 L 7.382812 4.800781 L 11.691406 4.800781 C 12.03125 4.800781 12.308594 5.070312 12.308594 5.398438 L 12.308594 7.199219 L 14.152344 7.199219 C 14.492188 7.199219 14.769531 7.46875 14.769531 7.800781 L 14.769531 9.601562 L 16.617188 9.601562 C 16.957031 9.601562 17.230469 9.867188 17.230469 10.199219 L 17.230469 12 L 19.078125 12 C 19.417969 12 19.691406 12.269531 19.691406 12.601562 L 19.691406 16.199219 C 19.691406 16.53125 19.417969 16.800781 19.078125 16.800781 L 17.230469 16.800781 L 17.230469 12 L 15.382812 12 C 15.042969 12 14.769531 11.730469 14.769531 11.398438 L 14.769531 9.601562 L 12.921875 9.601562 C 12.582031 9.601562 12.308594 9.332031 12.308594 9 L 12.308594 7.199219 L 7.382812 7.199219 L 7.382812 4.800781 L 4.921875 4.800781 L 4.921875 7.199219 L 2.460938 7.199219 L 2.460938 12 L 4.308594 12 C 4.648438 12 4.921875 12.269531 4.921875 12.601562 L 4.921875 14.398438 L 6.769531 14.398438 C 7.109375 14.398438 7.382812 14.667969 7.382812 15 L 7.382812 16.800781 L 12.308594 16.800781 L 12.308594 19.199219 L 14.769531 19.199219 L 14.769531 21 C 14.769531 21.332031 14.492188 21.601562 14.152344 21.601562 Z M 12.308594 16.800781 L 12.308594 15 C 12.308594 14.667969 12.582031 14.398438 12.921875 14.398438 L 14.152344 14.398438 C 14.492188 14.398438 14.769531 14.667969 14.769531 15 L 14.769531 16.800781 Z M 17.230469 18.601562 C 17.230469 18.929688 16.957031 19.199219 16.617188 19.199219 L 14.769531 19.199219 L 14.769531 16.800781 L 17.230469 16.800781 Z M 4.921875 7.199219 L 7.382812 7.199219 L 7.382812 9 C 7.382812 9.332031 7.109375 9.601562 6.769531 9.601562 L 5.539062 9.601562 C 5.199219 9.601562 4.921875 9.332031 4.921875 9 Z M 11.078125 12.601562 C 11.078125 12.929688 10.800781 13.199219 10.460938 13.199219 L 9.230469 13.199219 C 8.890625 13.199219 8.617188 12.929688 8.617188 12.601562 L 8.617188 11.398438 C 8.617188 11.070312 8.890625 10.800781 9.230469 10.800781 L 10.460938 10.800781 C 10.800781 10.800781 11.078125 11.070312 11.078125 11.398438 Z M 11.078125 12.601562", "line-chart": "M3.5,18.5L9.5,12.5L13.5,16.5L22,6.92L20.59,5.5L13.5,13.5L9.5,9.5L2,17L3.5,18.5Z", "bar-chart": "M3,22V8H7V22H3M10,22V2H14V22H10M17,22V14H21V22H17Z", "gantt-chart": "M2,5H10V2H12V22H10V18H6V15H10V13H4V10H10V8H2V5M14,5H17V8H14V5M14,10H19V13H14V10M14,15H22V18H14V15Z", - "crown": "M5 16L3 5L8.5 10L12 4L15.5 10L21 5L19 16H5M19 19C19 19.6 18.6 20 18 20H6C5.4 20 5 19.6 5 19V18H19V19Z" + "crown": "M5 16L3 5L8.5 10L12 4L15.5 10L21 5L19 16H5M19 19C19 19.6 18.6 20 18 20H6C5.4 20 5 19.6 5 19V18H19V19Z", + "earth": "M17.9,17.39C17.64,16.59 16.89,16 16,16H15V13A1,1 0 0,0 14,12H8V10H10A1,1 0 0,0 11,9V7H13A2,2 0 0,0 15,5V4.59C17.93,5.77 20,8.64 20,12C20,14.08 19.2,15.97 17.9,17.39M11,19.93C7.05,19.44 4,16.08 4,12C4,11.38 4.08,10.78 4.21,10.21L9,15V16A2,2 0 0,0 11,18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z", + "city": "M15,23H13V21H15V23M19,21H17V23H19V21M15,17H13V19H15V17M7,21H5V23H7V21M7,17H5V19H7V17M19,17H17V19H19V17M15,13H13V15H15V13M19,13H17V15H19V13M21,9A2,2 0 0,1 23,11V23H21V11H11V23H9V15H3V23H1V15A2,2 0 0,1 3,13H9V11A2,2 0 0,1 11,9V7A2,2 0 0,1 13,5H15V1H17V5H19A2,2 0 0,1 21,7V9M19,9V7H13V9H19Z", + "drop": "M12 18C12 18.7 12.12 19.36 12.34 20C12.23 20 12.12 20 12 20C8.69 20 6 17.31 6 14C6 10 12 3.25 12 3.25S16.31 8.1 17.62 12C16.93 12.06 16.28 12.22 15.67 12.47C15 10.68 13.5 8.33 12 6.39C10 8.96 8 12.23 8 14C8 16.21 9.79 18 12 18M19 17V14H17V17H14V19H17V22H19V19H22V17H19Z", + "faucet": "M21 21H3C3 19.9 3.9 19 5 19H19C20.11 19 21 19.89 21 21M19 7C19 5.39 17.93 3 15 3S11 5.39 11 7V18H13V7C13 6.54 13.17 5 15 5S17 6.54 17 7H16.5V9H19.5V7H19M7 12C6.45 12 6 12.45 6 13V14H3V15H6V18H8V13C8 12.45 7.55 12 7 12M21 14H18V13C18 12.45 17.55 12 17 12S16 12.45 16 13V18H18V15H21V14Z" } diff --git a/assets/styles/base.scss b/assets/styles/base.scss index 8151e013..4c4a465a 100644 --- a/assets/styles/base.scss +++ b/assets/styles/base.scss @@ -78,6 +78,7 @@ $grayscale: ( // --supply: #65c7f8; --supply: #1ca7ed; --staking: #85f891; + --geo-map: #8B8C8D; } [theme="dimmed"] { @@ -139,6 +140,7 @@ $grayscale: ( --block-progress-fill-background: #33a853; --logo-name: var(--txt-primary); --bar-fill: rgb(243, 147, 45); + --geo-map: #8B8C8D; } [theme="light"] { @@ -200,6 +202,7 @@ $grayscale: ( --block-progress-fill-background: #33a853; --logo-name: var(--txt-primary); --bar-fill: rgb(243, 147, 45); + --geo-map: #8B8C8D; } @font-face { diff --git a/components/DatePicker.vue b/components/DatePicker.vue index 5c45c4bb..19d08a54 100644 --- a/components/DatePicker.vue +++ b/components/DatePicker.vue @@ -19,15 +19,15 @@ const props = defineProps({ }, from: { type: [String, Number], - default: '', + default: "", }, to: { type: [String, Number], - default: '', + default: "", }, minDate: { type: String, - default: '', + default: "", }, showTitle: { type: Boolean, @@ -40,53 +40,53 @@ const emit = defineEmits(["onUpdate"]) const popoverStyles = computed(() => { if (!isMobile()) { return { - width: '380', - side: 'left', - direction: 'row', + width: "380", + side: "left", + direction: "row", calendar: { - width: '65%', - paddingLeft: '12px', - borderLeftWidth: '1px', - borderLeftStyle: 'solid', - borderImage: 'linear-gradient(to bottom, transparent 0%, var(--op-10) 20%, var(--op-10) 80%, transparent 100%) 1' - } + width: "65%", + paddingLeft: "12px", + borderLeftWidth: "1px", + borderLeftStyle: "solid", + borderImage: "linear-gradient(to bottom, transparent 0%, var(--op-10) 20%, var(--op-10) 80%, transparent 100%) 1", + }, } } else { return { - width: '250', - side: 'right', - direction: 'column', + width: "250", + side: "right", + direction: "column", calendar: { - width: '100%', - paddingTop: '12px', - borderTopWidth: '1px', - borderTopStyle: 'solid', - borderImage: 'linear-gradient(to right, transparent 0%, var(--op-10) 5%, var(--op-10) 95%, transparent 100%) 1' - } + width: "100%", + paddingTop: "12px", + borderTopWidth: "1px", + borderTopStyle: "solid", + borderImage: "linear-gradient(to right, transparent 0%, var(--op-10) 5%, var(--op-10) 95%, transparent 100%) 1", + }, } } }) const currentDate = ref(DateTime.now()) -const limitMinDate = ref(props.minDate ? DateTime.fromISO(props.minDate) : '') +const limitMinDate = ref(props.minDate ? DateTime.fromISO(props.minDate) : "") const month = ref(currentDate.value.month) const year = ref(currentDate.value.year) -const startDate = ref(props.from ? DateTime.fromSeconds(parseInt(props.from)).startOf('day') : {}) -const endDate = ref(props.to ? DateTime.fromSeconds(parseInt(props.to)).endOf('day') : {}) -const weekdays = ref(Info.weekdays('narrow', { locale: 'en-US' })) +const startDate = ref(props.from ? DateTime.fromSeconds(parseInt(props.from)).startOf("day") : {}) +const endDate = ref(props.to ? DateTime.fromSeconds(parseInt(props.to)).endOf("day") : {}) +const weekdays = ref(Info.weekdays("narrow", { locale: "en-US" })) const days = computed(() => { let rawDays = [] - const firstDay = DateTime.local(year.value, month.value).setLocale('en-US') - const lastDay = firstDay.endOf('month') + const firstDay = DateTime.local(year.value, month.value).setLocale("en-US") + const lastDay = firstDay.endOf("month") for (let day = firstDay; day <= lastDay; day = day.plus({ days: 1 })) { - rawDays.push(day) - } + rawDays.push(day) + } if (firstDay.weekday !== 1) { let prevDay = firstDay while (prevDay.weekday !== 1) { - prevDay = prevDay.minus({ days: 1 }).startOf('day') + prevDay = prevDay.minus({ days: 1 }).startOf("day") rawDays.unshift(prevDay) } } @@ -94,7 +94,7 @@ const days = computed(() => { if (lastDay.weekday !== 7) { let nextDay = lastDay while (nextDay.weekday !== 7) { - nextDay = nextDay.plus({ days: 1 }).startOf('day') + nextDay = nextDay.plus({ days: 1 }).startOf("day") rawDays.push(nextDay) } } @@ -109,32 +109,37 @@ const days = computed(() => { const periods = ref(STATS_PERIODS) const selectedPeriod = ref(props.period.value ? props.period : {}) -const selectedRange = ref('') +const selectedRange = ref("") const updateSelectedRange = (from, to) => { if (from?.ts) { if (to?.ts) { if (from.year === to.year) { - selectedRange.value = from.toFormat('dd LLL') !== to.toFormat('dd LLL') ? `${from.toFormat('dd LLL')} - ${to.toFormat('dd LLL')}` : from.toFormat('dd LLL') + selectedRange.value = + from.toFormat("dd LLL") !== to.toFormat("dd LLL") + ? `${from.toFormat("dd LLL")} - ${to.toFormat("dd LLL")}` + : from.toFormat("dd LLL") } else { - selectedRange.value = `${from.toFormat('dd LLL yyyy')} - ${to.toFormat('dd LLL yyyy')}` + selectedRange.value = `${from.toFormat("dd LLL yyyy")} - ${to.toFormat("dd LLL yyyy")}` } + month.value = to.c.month + year.value = to.c.year } else { - selectedRange.value = from.toFormat('dd LLL') + selectedRange.value = from.toFormat("dd LLL") } } else { - selectedRange.value = '' + selectedRange.value = "" } } updateSelectedRange(startDate.value, endDate.value) -const isNextMonthAvailable = true // computed(() => !(month.value === currentDate.value.month && year.value === currentDate.value.year)) -const isPrevMonthAvailable = true // computed(() => limitMinDate.value ? limitMinDate.value.ts < days.value[0][0].ts : true) +const isNextMonthAvailable = true +const isPrevMonthAvailable = true const isDayAvailable = (d) => { - if (d.startOf('day').ts > currentDate.value.startOf('day').ts) { + if (d.startOf("day").ts > currentDate.value.startOf("day").ts) { return false } else if (limitMinDate.value) { - return d.startOf('day').ts >= limitMinDate.value.startOf('day').ts + return d.startOf("day").ts >= limitMinDate.value.startOf("day").ts } else { return true } @@ -148,10 +153,14 @@ const handleSelectPeriod = (period) => { } else { selectedPeriod.value = period - startDate.value = DateTime.now().minus({ - days: period.timeframe === "day" ? period.value - 1 : 0, - }).startOf('day') - endDate.value = DateTime.now().endOf('day') + startDate.value = DateTime.now() + .minus({ + days: period.timeframe === "day" ? period.value - 1 : 0, + months: period.timeframe === "month" ? period.value : 0, + years: period.timeframe === "year" ? period.value : 0, + }) + .startOf("day") + endDate.value = DateTime.now().endOf("day") } } @@ -161,10 +170,10 @@ const handleSelectDate = (d) => { } else if (startDate.value.ts !== d.ts) { if (!endDate.value.ts) { if (startDate.value.ts > d.ts) { - endDate.value = startDate.value.endOf('day') + endDate.value = startDate.value.endOf("day") startDate.value = d } else { - endDate.value = d.endOf('day') + endDate.value = d.endOf("day") } } else { startDate.value = d @@ -174,7 +183,7 @@ const handleSelectDate = (d) => { if (!endDate.value.ts) { startDate.value = {} } else { - startDate.value = endDate.value.startOf('day') + startDate.value = endDate.value.startOf("day") endDate.value = {} } } @@ -183,11 +192,17 @@ const handleSelectDate = (d) => { } const isInSelectedPeriod = (d) => { - return startDate.value.ts < d.ts && d.endOf('day').ts < endDate.value.ts + return startDate.value.ts < d.ts && d.endOf("day").ts < endDate.value.ts } const isOpen = ref(false) const handleOpen = () => { + if (props.to) { + let to = DateTime.fromSeconds(parseInt(props.to)) + month.value = to.c.month + year.value = to.c.year + } + isOpen.value = true } const handleClose = () => { @@ -195,7 +210,7 @@ const handleClose = () => { if (!startDate.value.ts) { month.value = currentDate.value.month - year.value = currentDate.value.year + year.value = currentDate.value.year } } @@ -203,11 +218,14 @@ const handleApply = () => { isOpen.value = false if (!startDate.value?.ts) { - emit('onUpdate', { clear: true }) + emit("onUpdate", { clear: true }) } else { - endDate.value = endDate.value?.ts ? endDate.value?.endOf('day') : startDate.value?.endOf('day') + endDate.value = endDate.value?.ts ? endDate.value?.endOf("day") : startDate.value?.endOf("day") - emit('onUpdate', { from: parseInt(startDate.value.ts / 1_000), to: parseInt(endDate.value.ts / 1_000) }) + emit("onUpdate", { + from: parseInt(startDate.value.startOf("day").toSeconds()), + to: parseInt(endDate.value.endOf("day").toSeconds()), + }) } } @@ -218,18 +236,18 @@ const handleClear = () => { endDate.value = {} selectedPeriod.value = {} - emit('onUpdate', { clear: true }) + emit("onUpdate", { clear: true }) } const handleMonthChange = (v) => { switch (month.value + v) { case 0: month.value = 12 - year.value -- + year.value-- break case 13: month.value = 1 - year.value ++ + year.value++ break default: month.value += v @@ -243,7 +261,17 @@ const handleYearChange = (v) => { watch( () => props.from, () => { - updateSelectedRange(DateTime.fromSeconds(parseInt(props.from)), DateTime.fromSeconds(parseInt(props.to))) + startDate.value = props.from ? DateTime.fromSeconds(parseInt(props.from)).startOf("day") : {} + endDate.value = props.to ? DateTime.fromSeconds(parseInt(props.to)).endOf("day") : {} + updateSelectedRange(startDate.value, endDate.value) + }, +) + +watch( + () => props.to, + () => { + endDate.value = props.to ? DateTime.fromSeconds(parseInt(props.to)).endOf("day") : {} + updateSelectedRange(startDate.value, endDate.value) }, ) @@ -275,34 +303,49 @@ watch( - - + + Periods - - {{ period.title }} + + + {{ period.title }} + - + Periods - - {{ period.shortTitle }} + + + {{ period.shortTitle }} + - - + - {{ `${DateTime.local(year, month).toFormat('LLLL')} ${year}` }} + {{ `${DateTime.local(year, month).toFormat("LLLL")} ${year}` }} - - {{ d.day }} + > + {{ d.day }} + @@ -388,7 +438,7 @@ watch( - \ No newline at end of file + diff --git a/components/LeftSidebar.vue b/components/LeftSidebar.vue index cc34e8ce..b0f64344 100644 --- a/components/LeftSidebar.vue +++ b/components/LeftSidebar.vue @@ -10,7 +10,7 @@ import NavLink from "@/components/modules/navigation/NavLink.vue" /** Utils */ import { getNetworkName } from "@/services/utils/general" import { StatusMap } from "@/services/constants/node" -import { isMobile } from "@/services/utils" +import { isMainnet, isMobile } from "@/services/utils" /** Store */ import { useAppStore } from "~/store/app" @@ -52,16 +52,19 @@ const mainLinks = reactive([ name: "Active", path: "/validators?status=active&page=1", queryParam: {status: "active"}, + show: true, }, { name: "Jailed", path: "/validators?status=jailed&page=1", queryParam: {status: "jailed"}, + show: true, }, { name: "Inactive", path: "/validators?status=inactive&page=1", queryParam: {status: "inactive"}, + show: true, }, ], }, @@ -74,16 +77,25 @@ const mainLinks = reactive([ name: "General", path: "/stats?tab=general", queryParam: {tab: "general"}, + show: true, }, { name: "Blocks", path: "/stats?tab=blocks", queryParam: {tab: "blocks"}, + show: true, }, { name: "Rollups", path: "/stats?tab=rollups", queryParam: {tab: "rollups"}, + show: true, + }, + { + name: "Ecosystem", + path: "/stats?tab=ecosystem", + queryParam: {tab: "ecosystem"}, + show: isMainnet(), }, ], }, @@ -99,11 +111,13 @@ const modularLinks = reactive([ { name: "Cost Savings", path: "/calculators/savings", + show: true, }, { name: "Register rollup", path: "https://forms.gle/nimJyQJG4Lb4BTcG7", external: true, + show: true, }, ], }, @@ -115,6 +129,7 @@ const modularLinks = reactive([ { name: "Treemap View", path: "/namespaces/treemap", + show: true, }, ], }, @@ -127,6 +142,11 @@ const modularLinks = reactive([ const isToolsLinkCollapsed = ref(false) const toolsLinks = reactive([ + { + icon: "drop", + name: "Faucet", + path: "/faucet", + }, { icon: "blob", name: "Blobstream", @@ -164,6 +184,10 @@ const newLinks = reactive([ const handleNavigate = (url) => { window.location.replace(url) } + +const handleOnClose = () => { + appStore.showSidebar = false +} @@ -194,7 +218,7 @@ const handleNavigate = (url) => { - + @@ -209,7 +233,7 @@ const handleNavigate = (url) => { - + @@ -225,7 +249,7 @@ const handleNavigate = (url) => { - + @@ -351,9 +375,9 @@ const handleNavigate = (url) => { .group_title { border-radius: 5px; - cursor: default; + cursor: pointer; - padding: 4px 6px; + padding: 6px 6px; margin: 0 2px; transition: all 0.2s ease; diff --git a/components/OgImage/RollupImage.vue b/components/OgImage/RollupImage.vue index 64eac22a..b21934d2 100644 --- a/components/OgImage/RollupImage.vue +++ b/components/OgImage/RollupImage.vue @@ -3,7 +3,7 @@ import { DateTime } from "luxon" /** Services */ -import { formatBytes } from "@/services/utils" +import { comma, formatBytes } from "@/services/utils" defineOptions({ inheritAttrs: false, @@ -53,7 +53,7 @@ const bgStyles = computed(() => { Blobs: - {{ rollup.blobs_count }} + {{ comma(rollup.blobs_count) }} diff --git a/components/OgImage/StatsMetricImage.vue b/components/OgImage/StatsMetricImage.vue new file mode 100644 index 00000000..c4682e25 --- /dev/null +++ b/components/OgImage/StatsMetricImage.vue @@ -0,0 +1,39 @@ + + + + + + + + + stats + (' + + {{ series.title }} + + ') + + + + diff --git a/components/cmd/CommandMenu.vue b/components/cmd/CommandMenu.vue index 52c19ed2..ae8f4211 100644 --- a/components/cmd/CommandMenu.vue +++ b/components/cmd/CommandMenu.vue @@ -291,6 +291,15 @@ const rawNavigationActions = [ router.push("/calculators/savings") }, }, + { + type: "callback", + icon: "arrow-narrow-right", + title: "Go to Faucet", + runText: "Open Faucet", + callback: () => { + router.push("/faucet") + }, + }, { type: "callback", icon: "arrow-narrow-right", @@ -1454,7 +1463,7 @@ const runBounce = () => { - \ No newline at end of file + diff --git a/components/modules/stats/BarplotChartCard.vue b/components/modules/stats/BarplotChartCard.vue new file mode 100644 index 00000000..7da2f42b --- /dev/null +++ b/components/modules/stats/BarplotChartCard.vue @@ -0,0 +1,290 @@ + + + + + + {{ `By ${series.title}` }} + + + + + + + Amount: + {{ tooltip.amount }} + + + + + + + + + + diff --git a/components/modules/stats/ChartCardPreview.vue b/components/modules/stats/ChartCardPreview.vue index 1d9ac999..70eb6957 100644 --- a/components/modules/stats/ChartCardPreview.vue +++ b/components/modules/stats/ChartCardPreview.vue @@ -7,7 +7,7 @@ import { DateTime } from "luxon" import DiffChip from "@/components/modules/stats/DiffChip.vue" /** Services */ -import { abbreviate, comma, formatBytes, tia } from "@/services/utils" +import { abbreviate, comma, formatBytes, tia, truncateDecimalPart } from "@/services/utils" /** API */ import { fetchSeries, fetchSeriesCumulative, fetchTVS } from "@/services/api/stats" @@ -36,6 +36,11 @@ const diff = computed(() => { }) const chartEl = ref() const chartElPrev = ref() +const tooltipEl = ref() +const tooltip = ref({ + data: [], + show: false, +}) const getSeries = async () => { let data = [] @@ -59,7 +64,7 @@ const getSeries = async () => { period: 'day', from: parseInt( baseTime.minus({ - months: 2, + days: 60, }).ts / 1_000) }) } else if (props.series.name === "tvs") { @@ -77,8 +82,13 @@ const getSeries = async () => { })).reverse() } - prevData.value = data.slice(0, props.period.value).map((s) => ({ date: DateTime.fromISO(s.time).toJSDate(), value: parseFloat(s.value) })) - currentData.value = data.slice(props.period.value, data.length).map((s) => ({ date: DateTime.fromISO(s.time).toJSDate(), value: parseFloat(s.value) })) + if (props.series.aggregate === 'cumulative') { + prevData.value = data.slice(0, 30).map((s) => ({ date: DateTime.fromISO(s.time).toJSDate(), value: parseFloat(s.value) })) + currentData.value = data.slice(30, data.length).map((s) => ({ date: DateTime.fromISO(s.time).toJSDate(), value: parseFloat(s.value) })) + } else { + prevData.value = data.slice(0, props.period.value).map((s) => ({ date: DateTime.fromISO(s.time).toJSDate(), value: parseFloat(s.value) })) + currentData.value = data.slice(props.period.value, data.length).map((s) => ({ date: DateTime.fromISO(s.time).toJSDate(), value: parseFloat(s.value) })) + } if (props.series.name === 'block_time') { prevData.value = prevData.value @@ -98,8 +108,8 @@ const getSeries = async () => { } if (props.series.name === 'tvs') { - currentTotal.value = currentData.value[currentData.value.length - 1].value // Math.max(...currentData.value.map(d => d.value)) - prevTotal.value = prevData.value[prevData.value.length - 1].value // Math.max(...prevTotal.value.map(d => d.value)) + currentTotal.value = currentData.value[currentData.value.length - 1].value + prevTotal.value = prevData.value[prevData.value.length - 1].value } else if (props.series.aggregate !== 'cumulative') { currentTotal.value = currentData.value.reduce((sum, el) => { return sum + +el.value; @@ -118,12 +128,21 @@ const getSeries = async () => { currentTotal.value = currentTotal.value / currentData.value.length } + let pData = [] + prevData.value?.forEach((d, index) => { + pData.push({ + date: currentData.value[index]?.date, + realDate: d.date, + value: d.value, + }) + }) + prevData.value = pData + dataLoaded.value = true } const buildChart = (chart, data, color) => { - const width = chart.getBoundingClientRect().width - const height = chart.getBoundingClientRect().height + const { width, height } = chart.getBoundingClientRect() const marginTop = 8 const marginRight = 12 const marginBottom = 24 @@ -132,6 +151,33 @@ const buildChart = (chart, data, color) => { const MAX_VALUE = Math.max(Math.max(...prevData.value.map((s) => s.value)), Math.max(...currentData.value.map((s) => s.value))) const MIN_VALUE = Math.min(Math.min(...prevData.value.map((s) => s.value)), Math.min(...currentData.value.map((s) => s.value))) + function formatDate(date) { + if (props.period.timeframe === 'hour') { + return DateTime.fromJSDate(date).toFormat("LLL dd HH:mm") + } + + return DateTime.fromJSDate(date).toFormat("LLL dd") + } + + function formatValue(value) { + switch (props.series.units) { + case 'bytes': + return formatBytes(value) + case 'utia': + if (props.series.name === 'gas_price') { + return `${truncateDecimalPart(value, 4)} UTIA` + } + + return `${tia(value, 2)} TIA` + case 'seconds': + return `${truncateDecimalPart(value / 1_000, 3)}s` + case 'usd': + return `${abbreviate(value)} $` + default: + return comma(value) + } + } + /** Scale */ const x = d3.scaleUtc( d3.extent(data, (d) => d.date), @@ -153,7 +199,89 @@ const buildChart = (chart, data, color) => { .attr("preserveAspectRatio", "none") .attr("style", "max-width: 100%;") .style("-webkit-tap-highlight-color", "transparent") - // .on("touchstart", (event) => event.preventDefault()) + .on("pointerenter pointermove", onPointerMoved) + .on("pointerleave", onPointerleft) + .on("touchstart", (event) => event.preventDefault()) + + // This allows to find the closest X index of the mouse: + const bisect = d3.bisector(function(d) { return d.date; }).center + + const cFocus = svg + .append('g') + .append('circle') + .style("fill", "var(--brand)") + .attr('r', 4) + .style("opacity", 0) + .style("transition", "all 0.2s ease" ) + + const pFocus = svg + .append('g') + .append('circle') + .style("fill", "var(--txt-tertiary)") + .attr('r', 4) + .style("opacity", 0) + .style("transition", "all 0.2s ease" ) + + const focusLine = svg + .append('g') + .append('line') + .style("stroke-width", 2) + .style("stroke", "var(--op-15)") + .style("fill", "none") + .style("opacity", 0) + + function onPointerMoved(event) { + tooltip.value.show = true + + cFocus.style("opacity", 1) + focusLine.style("opacity", 1) + + // Recover coordinate we need + let idx = bisect(currentData.value, x.invert(d3.pointer(event)[0])) + let selectedCData = currentData.value[idx] + cFocus + .attr("cx", x(selectedCData.date)) + .attr("cy", y(selectedCData.value)) + focusLine + .attr("x1", x(selectedCData.date)) + .attr("y1", 0) + .attr("x2", x(selectedCData.date)) + .attr("y2", height) + + let tooltipWidth = tooltipEl.value?.wrapper ? tooltipEl.value?.wrapper?.getBoundingClientRect()?.width : 100 + let xPosition = x(selectedCData.date) + tooltip.value.x = (xPosition + tooltipWidth) > width + 5 ? xPosition - tooltipWidth - 15 : xPosition + 15 + tooltip.value.y = Math.min(y(selectedCData.value), height - 100) + + tooltip.value.data[0] = { + date: formatDate(selectedCData.date), + value: formatValue(selectedCData.value), + color: "var(--brand)", + } + tooltip.value.data.splice(1, 1) + if (prevData.value.length) { + let selectedPData = prevData.value[idx] + + pFocus + .attr("cx", x(selectedPData.date)) + .attr("cy", y(selectedPData.value)) + .style("opacity", 1) + + tooltip.value.data[1] = { + date: formatDate(selectedPData.realDate), + value: formatValue(selectedPData.value), + color: "var(--txt-tertiary)", + } + } + + } + + function onPointerleft() { + tooltip.value.show = false + cFocus.style("opacity", 0) + pFocus.style("opacity", 0) + focusLine.style("opacity", 0) + } /** Default Horizontal Lines */ svg.append("path") @@ -207,7 +335,7 @@ const buildChart = (chart, data, color) => { const drawChart = async () => { await getSeries() - buildChart(chartEl.value.wrapper, currentData.value, "var(--mint)") + buildChart(chartEl.value.wrapper, currentData.value, "var(--brand)") buildChart(chartElPrev.value.wrapper, prevData.value, "var(--txt-tertiary)") } @@ -221,7 +349,6 @@ watch( await drawChart() }, ) - @@ -276,15 +403,53 @@ watch( + + + + + + {{ d.value }} + + + + + {{ d.date }} + + + + + + + + + + + + - {{ DateTime.now().minus({ - months: 2, - }).toFormat("LLL dd") - }} + {{ DateTime.fromJSDate(currentData[0]?.date).toFormat("LLL dd") }} {{ DateTime.now().minus({ @@ -346,10 +511,41 @@ watch( bottom: 0; left: 0; right: 0; +} - /* border-top: 2px solid var(--op-5); +.tooltip_wrapper { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; - padding-top: 8px; */ + & .tooltip { + min-width: 100px; + pointer-events: none; + position: absolute; + z-index: 10; + + background: var(--card-background); + border-radius: 6px; + box-shadow: inset 0 0 0 1px var(--op-5), 0 14px 34px rgba(0, 0, 0, 15%), 0 4px 14px rgba(0, 0, 0, 5%); + + padding: 10px; + + transition: all 0.2s ease; + } + + & .legend { + height: 8px; + width: 8px; + border-radius: 50%; + } + + & .horizontal_divider { + width: 100%; + height: 1px; + background: var(--op-5); + } } @media (max-width: 1000px) { diff --git a/components/modules/stats/GeoMap.vue b/components/modules/stats/GeoMap.vue new file mode 100644 index 00000000..aa89cd55 --- /dev/null +++ b/components/modules/stats/GeoMap.vue @@ -0,0 +1,632 @@ + + + + + + + + + + + + + + + + + Somewhere in.. + + means that it was not possible to determine the exact location. + + + + + + + + + + + + + + + Loading map.. + + + + + diff --git a/components/modules/stats/InsightCard.vue b/components/modules/stats/InsightCard.vue index 1108a0b5..277e205e 100644 --- a/components/modules/stats/InsightCard.vue +++ b/components/modules/stats/InsightCard.vue @@ -10,7 +10,7 @@ import DiffChip from "@/components/modules/stats/DiffChip.vue" import { abbreviate, capitilize, comma, formatBytes, shareOfTotal, shareOfTotalString } from "@/services/utils" /** API */ -import { fetch24hDiffs, fetchSummary } from "@/services/api/stats" +import { fetchGeneralStats, fetchSummary } from "@/services/api/stats" const props = defineProps({ series: { @@ -67,7 +67,7 @@ const getSeries = async () => { }, ] } else { - data = await fetch24hDiffs({ name: props.series.name }) + data = await fetchGeneralStats({ name: props.series.name }) } switch (props.series.name) { diff --git a/components/modules/stats/LineChart.vue b/components/modules/stats/LineChart.vue index 68d43e68..54f16c31 100644 --- a/components/modules/stats/LineChart.vue +++ b/components/modules/stats/LineChart.vue @@ -14,36 +14,27 @@ const props = defineProps({ }) // TO DO: Fetch data if series.currentData is null -const currentData = computed(() => { return {data: props.series.currentData}}) -const prevData = computed(() => { - let data = [] - props.series.prevData?.forEach((d, index) => { - data.push({ - date: currentData.value?.data[index].date, - realDate: d.date, - value: d.value, - }) - }) - return { data: data } +const currentData = computed(() => { + return { data: props.series.currentData } }) + const chartEl = ref() const tooltip = ref({ data: [], show: false, }) -const buildChart = (chart, cData, pData, onEnter, onLeave) => { - const width = chart.getBoundingClientRect().width - const height = chart.getBoundingClientRect().height +const buildChart = (chart, cData, onEnter, onLeave) => { + const { width, height } = chart.getBoundingClientRect() const marginTop = 6 const marginRight = 12 const marginBottom = 24 const marginLeft = 12 const marginAxisX = 20 - const MIN_VALUE = d3.min([...cData.data?.map(s => s.value), ...pData.data?.map(s => s.value)]) - const MAX_VALUE = d3.max([...cData.data?.map(s => s.value), ...pData.data?.map(s => s.value)]) + const MIN_VALUE = d3.min([...cData.data?.map((s) => s.value)]) + const MAX_VALUE = d3.max([...cData.data?.map((s) => s.value)]) /** Scales */ const x = d3.scaleUtc( @@ -58,8 +49,8 @@ const buildChart = (chart, cData, pData, onEnter, onLeave) => { .curve(d3.curveCatmullRom) function formatDate(date) { - if (props.series.timeframe === 'hour') { - return DateTime.fromJSDate(date).toFormat("LLL dd, HH:mm") + if (props.series?.timeframe?.timeframe === "hour") { + return DateTime.fromJSDate(date).toFormat("HH:mm, LLL dd, yyyy") } return DateTime.fromJSDate(date).toFormat("LLL dd, yyyy") @@ -67,17 +58,17 @@ const buildChart = (chart, cData, pData, onEnter, onLeave) => { function formatValue(value) { switch (props.series.units) { - case 'bytes': + case "bytes": return formatBytes(value) - case 'utia': - if (props.series.name === 'gas_price') { + case "utia": + if (props.series.name === "gas_price") { return `${truncateDecimalPart(value, 4)} UTIA` } return `${tia(value, 2)} TIA` - case 'seconds': + case "seconds": return `${truncateDecimalPart(value / 1_000, 3)}s` - case 'usd': + case "usd": return `${abbreviate(value)} $` default: return comma(value) @@ -106,54 +97,45 @@ const buildChart = (chart, cData, pData, onEnter, onLeave) => { .on("pointerleave", onPointerleft) .on("touchstart", (event) => event.preventDefault()) - /** Add axes */ + /** Add axes */ svg.append("g") .attr("transform", "translate(0," + (height - marginAxisX) + ")") .attr("color", "var(--op-20)") - .call(d3.axisBottom(x).ticks(6).tickFormat(d3.timeFormat(props.series.timeframe === 'hour' ? "%H:%M" : "%b %d"))) - + .call( + d3 + .axisBottom(x) + .ticks(Math.min(cData.data.length, 6)) + .tickFormat(d3.timeFormat(["hour", "day"].includes(props.series.timeframe.timeframe) ? "%b %d" : "%b")) + ) + svg.append("g") .attr("transform", `translate(0,0)`) .attr("color", "var(--op-20)") - .call(d3.axisRight(y) - .ticks(4) - .tickSize(width) - .tickFormat(formatScaleValue)) - .call(g => g.select(".domain") - .remove()) - .call(g => g.selectAll(".tick line") - .attr("stroke-opacity", 0.7) - .attr("stroke-dasharray", "10, 10")) - .call(g => g.selectAll(".tick text") - .attr("x", 4) - .attr("dy", -4)) + .call(d3.axisRight(y).ticks(4).tickSize(width).tickFormat(formatScaleValue)) + .call((g) => g.select(".domain").remove()) + .call((g) => g.selectAll(".tick line").attr("stroke-opacity", 0.7).attr("stroke-dasharray", "10, 10")) + .call((g) => g.selectAll(".tick text").attr("x", 4).attr("dy", -4)) // This allows to find the closest X index of the mouse: - const bisect = d3.bisector(function(d) { return d.date; }).center + const bisect = d3.bisector(function (d) { + return d.date + }).center const cFocus = svg - .append('g') - .append('circle') - .style("fill", cData.color) - .attr('r', 4) - .style("opacity", 0) - .style("transition", "all 0.2s ease" ) - - const pFocus = svg - .append('g') - .append('circle') - .style("fill", pData.color) - .attr('r', 4) - .style("opacity", 0) - .style("transition", "all 0.2s ease" ) + .append("g") + .append("circle") + .style("fill", cData.color) + .attr("r", 4) + .style("opacity", 0) + .style("transition", "all 0.2s ease") const focusLine = svg - .append('g') - .append('line') - .style("stroke-width", 2) - .style("stroke", "var(--op-15)") - .style("fill", "none") - .style("opacity", 0) + .append("g") + .append("line") + .style("stroke-width", 2) + .style("stroke", "var(--op-15)") + .style("fill", "none") + .style("opacity", 0) function onPointerMoved(event) { onEnter() @@ -163,51 +145,34 @@ const buildChart = (chart, cData, pData, onEnter, onLeave) => { // Recover coordinate we need let idx = bisect(cData.data, x.invert(d3.pointer(event)[0])) let selectedCData = cData.data[idx] - cFocus - .attr("cx", x(selectedCData.date)) - .attr("cy", y(selectedCData.value)) + cFocus.attr("cx", x(selectedCData.date)).attr("cy", y(selectedCData.value)) focusLine .attr("x1", x(selectedCData.date)) .attr("y1", 0) .attr("x2", x(selectedCData.date)) .attr("y2", height - marginAxisX) - + let xPosition = x(selectedCData.date) - tooltip.value.x = xPosition > (width - 200) ? xPosition - 215 : xPosition + 15 + tooltip.value.x = xPosition > width - 200 ? xPosition - 215 : xPosition + 15 tooltip.value.y = Math.min(y(selectedCData.value), height - 100) - + tooltip.value.data[0] = { date: formatDate(selectedCData.date), value: formatValue(selectedCData.value), color: cData.color, } tooltip.value.data.splice(1, 1) - if (pData.data.length) { - let selectedPData = pData.data[idx] - - pFocus - .attr("cx", x(selectedPData.date)) - .attr("cy", y(selectedPData.value)) - .style("opacity", 1) - - tooltip.value.data[1] = { - date: formatDate(selectedPData.realDate), - value: formatValue(selectedPData.value), - color: pData.color, - } - } - } function onPointerleft() { onLeave() cFocus.style("opacity", 0) - pFocus.style("opacity", 0) focusLine.style("opacity", 0) } /** Chart Lines */ - const cPath = svg.append("path") + const cPath = svg + .append("path") .attr("fill", "none") .attr("stroke", cData.color) .attr("stroke-width", 2) @@ -215,50 +180,32 @@ const buildChart = (chart, cData, pData, onEnter, onLeave) => { .attr("stroke-linejoin", "round") .attr("d", line(cData.data.filter((item) => item.value !== null))) - const pPath = svg.append("path") - .attr("fill", "none") - .attr("stroke", pData.color) - .attr("stroke-width", 2) - .attr("stroke-linecap", "round") - .attr("stroke-linejoin", "round") - .attr("d", line(pData.data?.filter((item) => item.value !== null))) - if (chart.children[0]) chart.children[0].remove() chart.append(svg.node()) - const cTotalLength = cPath.node().getTotalLength(); - const pTotalLength = pPath.node().getTotalLength(); + const cTotalLength = cPath.node().getTotalLength() - cPath.attr("stroke-dasharray", `${cTotalLength} ${cTotalLength}`) + cPath + .attr("stroke-dasharray", `${cTotalLength} ${cTotalLength}`) .attr("stroke-dashoffset", cTotalLength) .transition() .duration(1_000) .ease(d3.easeLinear) - .attr("stroke-dashoffset", 0); - - pPath.attr("stroke-dasharray", `${pTotalLength} ${pTotalLength}`) - .attr("stroke-dashoffset", pTotalLength) - .transition() - .duration(1_000) - .ease(d3.easeLinear) .attr("stroke-dashoffset", 0) - } const drawChart = () => { - currentData.value.color = "var(--mint)" - prevData.value.color = "var(--txt-tertiary)" + currentData.value.color = "var(--brand)" buildChart( chartEl.value.wrapper, currentData.value, - prevData.value, () => (tooltip.value.show = true), () => (tooltip.value.show = false), ) } watch( - () => [currentData.value, prevData.value], + () => [currentData.value], () => { if (chartEl?.value?.wrapper) { drawChart() @@ -269,9 +216,8 @@ watch( onMounted(() => { if (chartEl?.value?.wrapper) { drawChart() - } + } }) - @@ -286,19 +232,13 @@ onMounted(() => { gap="12" :class="$style.tooltip" > - + {{ d.value }} - + {{ d.date }} @@ -307,7 +247,7 @@ onMounted(() => { @@ -323,7 +263,7 @@ onMounted(() => { - \ No newline at end of file diff --git a/components/modules/stats/TimelineSlider.vue b/components/modules/stats/TimelineSlider.vue new file mode 100644 index 00000000..6407f16f --- /dev/null +++ b/components/modules/stats/TimelineSlider.vue @@ -0,0 +1,860 @@ + + + + + + + + + + + diff --git a/components/modules/stats/tabs/EcosystemTab.vue b/components/modules/stats/tabs/EcosystemTab.vue new file mode 100644 index 00000000..fe0feba9 --- /dev/null +++ b/components/modules/stats/tabs/EcosystemTab.vue @@ -0,0 +1,146 @@ + + + + + + Light Node Distribution + + + + + + + + + + + \ No newline at end of file diff --git a/components/modules/stats/tabs/GeneralTab.vue b/components/modules/stats/tabs/GeneralTab.vue index a15ba6a7..6c5508bc 100644 --- a/components/modules/stats/tabs/GeneralTab.vue +++ b/components/modules/stats/tabs/GeneralTab.vue @@ -14,7 +14,7 @@ import InsightCard from "@/components/modules/stats/InsightCard.vue" import { getInsightsByGroup, getSeriesByGroupAndType, STATS_PERIODS } from "@/services/constants/stats.js" /** API */ -import { fetch24hDiffs } from "@/services/api/stats.js" +import { fetchGeneralStats } from "@/services/api/stats.js" /** Store */ import { useAppStore } from "@/store/app" @@ -62,7 +62,7 @@ const insights = computed(() => getInsightsByGroup('General')) const get24hDiffs = async () => { isLoading.value = true - const data = await fetch24hDiffs({ name: 'changes_24h' }) + const data = await fetchGeneralStats({ name: 'changes_24h' }) diffs24h.value = data isLoading.value = false diff --git a/components/modules/stats/tabs/RollupsTab.vue b/components/modules/stats/tabs/RollupsTab.vue index 5a8c99fa..c2c21d04 100644 --- a/components/modules/stats/tabs/RollupsTab.vue +++ b/components/modules/stats/tabs/RollupsTab.vue @@ -17,7 +17,7 @@ import Popover from "@/components/ui/Popover.vue" import { getSeriesByGroupAndType } from "@/services/constants/stats.js" /** Services */ -import { capitilize, capitalizeAndReplaceUnderscore } from "@/services/utils" +import { capitilize, capitalizeAndReplaceUnderscore, isMainnet } from "@/services/utils" /** API */ import { fetchRollups, fetchRollupsDailyStats } from "@/services/api/rollup.js" @@ -266,7 +266,7 @@ watch( - + Economics diff --git a/components/shared/AdvBanner.vue b/components/shared/AdvBanner.vue index eea02784..fa816677 100644 --- a/components/shared/AdvBanner.vue +++ b/components/shared/AdvBanner.vue @@ -17,11 +17,15 @@ const props = defineProps({ }, }) +const router = useRouter() + const adv = ref({}) const isDisplayed = ref(true) const handleClick = () => { - if (adv.value.link) { + if (adv.value.internalLink) { + router.push(adv.value.internalLink) + } else if (adv.value.link) { window.open(adv.value.link, '_blank') } else if (adv.value.modal) { modalsStore.open(adv.value.modal) diff --git a/components/widgets/StakingWidget.vue b/components/widgets/StakingWidget.vue index 76857323..34439c49 100644 --- a/components/widgets/StakingWidget.vue +++ b/components/widgets/StakingWidget.vue @@ -20,12 +20,12 @@ const wrapperEl = ref(null) const wrapperWidth = ref(0) const barWidth = computed(() => Math.round(wrapperWidth.value - 32)) -const totalSupply = computed(() => lastHead.value.total_supply / 1_000_000) +const totalSupply = computed(() => lastHead.value?.total_supply / 1_000_000) const totalSupplyUSD = computed(() => totalSupply.value * currentPrice.value?.close) -const totalVotingPower = computed(() => lastHead.value.total_voting_power) +const totalVotingPower = computed(() => lastHead.value?.total_voting_power) const totalVotingPowerUSD = computed(() => totalVotingPower.value * currentPrice.value?.close) -const bondedShare = computed(() => shareOfTotal(lastHead?.value.total_voting_power * 1_000_000, lastHead?.value.total_supply, 2)) +const bondedShare = computed(() => shareOfTotal(lastHead.value?.total_voting_power * 1_000_000, lastHead.value?.total_supply, 2)) const isRefetching = ref(true) const totalValidators = ref(0) diff --git a/nuxt.config.ts b/nuxt.config.ts index e4d28779..3017c927 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -5,146 +5,148 @@ import topLevelAwait from "vite-plugin-top-level-await" import path from "path" export default defineNuxtConfig({ - modules: ["nuxt-simple-sitemap", "@pinia/nuxt", "nuxt-og-image"], + modules: ["nuxt-simple-sitemap", "@pinia/nuxt", "nuxt-og-image"], - site: { - url: "https://celenium.io", + site: { + url: "https://celenium.io", }, - sitemap: { - xsl: false, + sitemap: { + xsl: false, }, - routeRules: { - "/node": { - ssr: false, - }, - "/": { - sitemap: { - changefreq: "daily", - priority: 1, - }, - }, - "/blocks": { - sitemap: { - changefreq: "daily", - priority: 0.9, - }, - }, - "/namespaces": { - sitemap: { - changefreq: "daily", - priority: 0.8, - }, - }, - "/txs": { - sitemap: { - changefreq: "daily", - priority: 0.7, - }, - }, - "/addresses": { - sitemap: { - changefreq: "daily", - priority: 0.6, - }, - }, - "/gas": { - sitemap: { - changefreq: "daily", - priority: 0.5, - }, - }, - "/namespaces/treemap": { - sitemap: { - changefreq: "weekly", - priority: 0.4, - }, - }, + routeRules: { + "/node": { + ssr: false, + }, + "/": { + sitemap: { + changefreq: "daily", + priority: 1, + }, + }, + "/blocks": { + sitemap: { + changefreq: "daily", + priority: 0.9, + }, + }, + "/namespaces": { + sitemap: { + changefreq: "daily", + priority: 0.8, + }, + }, + "/txs": { + sitemap: { + changefreq: "daily", + priority: 0.7, + }, + }, + "/addresses": { + sitemap: { + changefreq: "daily", + priority: 0.6, + }, + }, + "/gas": { + sitemap: { + changefreq: "daily", + priority: 0.5, + }, + }, + "/namespaces/treemap": { + sitemap: { + changefreq: "weekly", + priority: 0.4, + }, + }, }, - runtimeConfig: { - public: { - AMP: process.env.AMP, - }, + runtimeConfig: { + public: { + AMP: process.env.AMP, + }, }, - app: { - head: { - htmlAttrs: { - lang: "en", - }, - meta: [ - { name: "viewport", content: "width=device-width, initial-scale=1, maximum-scale=1" }, - { - name: "lang", - content: "en", - }, - ], - link: [ - { - id: "favicon", - rel: "icon", - type: "image/png", - }, - { - rel: "preconnect", - href: "https://fonts.googleapis.com", - }, - { - rel: "preconnect", - href: "https://fonts.gstatic.com", - crossorigin: "anonymous", - }, - { - rel: "preload", - as: "style", - href: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap", - onload: "this.onload=null;this.rel='stylesheet'", - }, - { - rel: "preload", - as: "style", - href: "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@700&display=swap", - onload: "this.onload=null;this.rel='stylesheet'", - }, - { - rel: "preload", - as: "style", - href: "https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap", - onload: "this.onload=null;this.rel='stylesheet'", - }, - ], - }, + app: { + head: { + htmlAttrs: { + lang: "en", + }, + meta: [ + { name: "viewport", content: "width=device-width, initial-scale=1, maximum-scale=1" }, + { + name: "lang", + content: "en", + }, + ], + link: [ + { + id: "favicon", + rel: "icon", + type: "image/png", + }, + { + rel: "preconnect", + href: "https://fonts.googleapis.com", + }, + { + rel: "preconnect", + href: "https://fonts.gstatic.com", + crossorigin: "anonymous", + }, + { + rel: "preload", + as: "style", + href: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap", + onload: "this.onload=null;this.rel='stylesheet'", + }, + { + rel: "preload", + as: "style", + href: "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@700&display=swap", + onload: "this.onload=null;this.rel='stylesheet'", + }, + { + rel: "preload", + as: "style", + href: "https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap", + onload: "this.onload=null;this.rel='stylesheet'", + }, + ], + }, }, - css: ["@/assets/styles/base.scss", "@/assets/styles/flex.scss", "@/assets/styles/text.scss"], + css: ["@/assets/styles/base.scss", "@/assets/styles/flex.scss", "@/assets/styles/text.scss"], - pinia: { - autoImports: ["defineStore"], + pinia: { + autoImports: ["defineStore"], }, - ogImage: { - fonts: ["Inter:400", "Inter:600", "IBM+Plex+Mono:400"], + ogImage: { + fonts: ["Inter:400", "Inter:600", "IBM+Plex+Mono:400"], }, - devtools: { - enabled: false, + devtools: { + enabled: false, }, - vite: { - define: { - global: "globalThis", - }, - resolve: { - alias: { - "unenv/runtime/node/buffer/index/": path.resolve(__dirname, "./node_modules/buffer/index"), - }, - }, - plugins: [wasm(), topLevelAwait(), nodePolyfills()], - worker: { - format: "es", - plugins: () => [wasm(), topLevelAwait()], - }, + vite: { + define: { + global: "globalThis", + }, + resolve: { + alias: { + "unenv/runtime/node/buffer/index/": path.resolve(__dirname, "./node_modules/buffer/index"), + }, + }, + plugins: [wasm(), topLevelAwait(), nodePolyfills()], + worker: { + format: "es", + plugins: () => [wasm(), topLevelAwait()], + }, }, -}) + + compatibilityDate: "2025-04-02", +}) \ No newline at end of file diff --git a/package.json b/package.json index 127f34db..c25ede42 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "codemirror": "^6.0.1", "crypto-js": "^4.2.0", "d3": "^7.8.5", + "d3-geo": "^3.1.1", "d3-hierarchy": "^3.1.2", "focus-trap": "7.5.2", "iconv-lite": "^0.6.3", diff --git a/pages/calculators/savings.vue b/pages/calculators/savings.vue index 6d5e7a92..3a776a37 100644 --- a/pages/calculators/savings.vue +++ b/pages/calculators/savings.vue @@ -304,414 +304,424 @@ useHead({ - - - - - - Cost Saving Calculator - - - - Rollup Estimation Cost - - Discover the potential cost benefits of building on Celestia, modular blockchain network. - - - + + + + + + + + + Cost Saving Calculator + - - - - - Rollup Stack - + + Rollup Estimation Cost + + Discover the potential cost benefits of building on Celestia, modular blockchain network. + + - Rollup underlying technology (SDK). - - - - - - - - - {{ stack.name }} - - - ${{ comma(getAvgCallDataCostByStack(stack.name)) }} - - Unavailable - + + + + + Rollup Stack + - - - {{ percentDiff(rollupStacks[selectedRollupStack].price, rollupStacks[idx].price).pos ? "-" : "+" - }}{{ percentDiff(rollupStacks[selectedRollupStack].price, rollupStacks[idx].price).val.toFixed(2) }}% - - - - - + Rollup underlying technology (SDK). + - - - - - Transaction Type - - + + + + - Type of transaction used to calculate the total size of posted data. - - - - - - - - - {{ stack.name }} - {{ stack.description }} + + {{ stack.name }} + + + ${{ comma(getAvgCallDataCostByStack(stack.name)) }} + + Unavailable + - - - - + + + {{ percentDiff(rollupStacks[selectedRollupStack].price, rollupStacks[idx].price).pos ? "-" : "+" + }}{{ percentDiff(rollupStacks[selectedRollupStack].price, rollupStacks[idx].price).val.toFixed(2) }}% + + + - - - - + + - Expected transactions + Transaction Type - Total number of posted transactions. + Type of transaction used to calculate the total size of posted data. - + + + + - - {{ comma(txs) }} - - - - - - + + {{ stack.name }} + {{ stack.description }} + + + + + + - + - - - - - - Using L2 the expected cost ~${{ comma(expectedCostL2, ",", 0) }}, with Celestia ~${{ comma(expectedCostCelestia, ",", 0) }} for - {{ abbreviate(txs) }} transactions - - - - - - - ${{ comma(savingsUsingCelestia, ",", 0) }} - - You'll save using Celestia - + + + + + + Expected transactions + + - - ${{ tiaPerMb.toFixed(5) }} - Price per Mb in Celestia - + Total number of posted transactions. + + + + + + {{ comma(txs) }} + + + + + + + + - - - - ~{{ payLessPercentCelestia.toFixed(2) }}% - - You'll pay % less + + + + + + Using L2 the expected cost ~${{ comma(expectedCostL2, ",", 0) }}, with Celestia ~${{ comma(expectedCostCelestia, ",", 0) }} for + {{ abbreviate(txs) }} transactions + + + + + + + ${{ comma(savingsUsingCelestia, ",", 0) }} + + You'll save using Celestia + + + + ${{ tiaPerMb.toFixed(5) }} + Price per Mb in Celestia + - - - ${{ comma(additionalSettlementCost, ",", 0) }} - - Settlement Cost + + + + ~{{ payLessPercentCelestia.toFixed(2) }}% + + You'll pay % less + + + + + ${{ comma(additionalSettlementCost, ",", 0) }} + + Settlement Cost + - - - + + - - This estimation assumes a constant $TIA price. It should only be used as an estimation and - reference to compare what-if scenarios. - + + This estimation assumes a constant $TIA price. It should only be used as an estimation and + reference to compare what-if scenarios. + + - - - - - - - Constants - - - - Reset - - - - - Variables used to calculate the savings of Celestia's usage. - - - - Editable variables - - - - Static - + + + + + + Constants + + + Reset - - - - - - - - TIA Price - - - - ${{ tiaPrice }} - - - + + + + Variables used to calculate the savings of Celestia's usage. + + + + Editable variables + - - - + + Static - + + + - - Average Gas - {{ comma(averageGas) }} - - + + + + + TIA Price + + + + ${{ tiaPrice }} + + + + + + + + + + - - - Price per gas + + Average Gas + {{ comma(averageGas) }} + + - - - {{ pricePerGas }} UTIA - - - - - - - - - + + + Price per gas + + + + {{ pricePerGas }} UTIA + + + + + + + + + + - - - - - - - Avg batch size - - - {{ formatBytes(batchSize) }} / block - - - - - - - - - + + + + + Avg batch size + + + + {{ formatBytes(batchSize) }} / block + + + + + + + + + + - - - - Avg num of shares - 215 + + + Avg num of shares + 215 + - - - - Use EIP-4844 + + + Use EIP-4844 + - - + --> + + + + + + + Build with Celenium API + - - - - - - Build with Celenium API + + Unlock the power of Celestia: Scalable, Secure and Modular Blockchain. + - - Unlock the power of Celestia: Scalable, Secure and Modular Blockchain. - + Get started -> - - Get started -> - - + + @@ -721,6 +731,10 @@ useHead({ padding: 20px 24px 60px 24px; } +.breadcrumbs { + margin-bottom: 16px; +} + .left { } diff --git a/pages/faucet.vue b/pages/faucet.vue new file mode 100644 index 00000000..e35f5fa1 --- /dev/null +++ b/pages/faucet.vue @@ -0,0 +1,726 @@ + + + + + + + + + + Celenium Faucet + + + + Faucet is only available for + Mocha + and + Arabica + networks. + + + + + + + + Network + + Mocha + Arabica + + + + + + + + + + {{ validation.title }} + + + + + + + + + Recieve 0.5 TIA + + + + + + + + + + + + + + Faucet + + + + {{ comma(tia(faucetBalance, 2)) }} TIA + + + + + + {{ splitAddress(faucetAddress) }} + + + + + + + + + + + + {{ account?.hash + ? $getDisplayName("addresses", "", account) === splitAddress(account?.hash) + ? 'Address' + : $getDisplayName("addresses", "", account) + : 'Address' + }} + + + + {{ tia(account?.balance?.spendable || 0, 2) }} TIA + + + + + {{ (account?.hash || (address && validation.type !== 'error')) + ? splitAddress(account?.hash || address) + : 'celestia ••• celestia' + }} + + + + + + + + + + + + + + + View Tx + + + + + + {{ executionResult.message }} + + + + + + + + Please do not forget to + + return + + the test tokens if you no longer need them so that they can be used by other developers and testers. + + + + + + Frequently asked questions + + + + handleOpenQuestion(1)" align="center" justify="between" gap="12" :class="$style.head"> + What is the Celestia Faucet? + + + + + + + The Celestia testnet faucet is a service that provides free testnet tokens for developers and users who want to interact with the Celestia blockchain without spending real assets. These tokens have no monetary value and are solely for testing purposes. + + + + handleOpenQuestion(2)" align="center" justify="between" gap="12" :class="$style.head"> + How can I request testnet tokens? + + + + + + + You can request testnet tokens by entering your Celestia testnet address, and clicking the "Received 0.5 TIA" button. Tokens will be sent to your wallet within a few moments. + + + + handleOpenQuestion(3)" align="center" justify="between" gap="12" :class="$style.head"> + What are the limitations on requesting tokens? + + + + + + + To ensure fair distribution and prevent abuse, the faucet has the following limitations: + • You can receive 0.5 TIA per request. + • You can request tokens only once per hour per IP address or wallet address. + • If you reach the limit, you will need to wait before requesting again. + + + + handleOpenQuestion(4)" align="center" justify="between" gap="12" :class="$style.head"> + What should I do if I don’t receive my tokens? + + + + + + + If you haven't received your tokens: + • Double-check that you entered the correct Celestia address. + • Ensure you are using one of supported networks. + • If the issue persists, contact support. + + + + handleOpenQuestion(5)" align="center" justify="between" gap="12" :class="$style.head"> + Can I use testnet tokens on the mainnet? + + + + + + + No, testnet tokens are only for testing on the Celestia testnets. They cannot be transferred or used on the Celestia mainnet. + + + + handleOpenQuestion(6)" align="center" justify="between" gap="12" :class="$style.head"> + What happens if I run out of testnet tokens? + + + + + + + If you run out of testnet tokens, you can request more from the faucet (subject to request limits). You may also check community channels for alternative ways to obtain test tokens. + + + + handleOpenQuestion(7)" align="center" justify="between" gap="12" :class="$style.head"> + How can I contribute to the Celestia testnet? + + + + + + + If you no longer need the testnet tokens, please return them to the faucet so that other developers and testers can use them. You can send them back to the + + faucet + + . + Your contribution helps keep the testnet accessible for everyone! + + + + + + + + + + diff --git a/pages/gas.vue b/pages/gas.vue index 12f41d2f..d8ba2341 100644 --- a/pages/gas.vue +++ b/pages/gas.vue @@ -49,42 +49,46 @@ useHead({ link: [ { rel: "canonical", - href: `https://celenium.io${route.path}`, + href: "https://celenium.io/gas", }, ], meta: [ { name: "description", - content: `Gas Tracker for Celestia Blockchain. Gas price, efficiency, etc.`, + content: "Gas Tracker for Celestia Blockchain. Gas price, efficiency, etc.", }, { property: "og:title", - content: `Celestia Gas Tracker - Celenium`, + content: "Celestia Gas Tracker - Celenium", }, { property: "og:description", - content: `Gas Tracker for Celestia Blockchain. Gas price, efficiency, etc.`, + content: "Gas Tracker for Celestia Blockchain. Gas price, efficiency, etc.", }, { property: "og:url", - content: `https://celenium.io${route.path}`, + content: "https://celenium.io/gas", }, { property: "og:image", - content: `https://celenium.io${route.path}__og_image__/og.png`, + content: "/img/seo/gas.png", }, { name: "twitter:title", - content: `Celestia Gas Tracker - Celenium`, + content: "Celestia Gas Tracker - Celenium", }, { name: "twitter:description", - content: `Gas Tracker for Celestia Blockchain. Gas price, efficiency, etc.`, + content: "Gas Tracker for Celestia Blockchain. Gas price, efficiency, etc.", }, { name: "twitter:card", content: "summary_large_image", }, + { + name: "twitter:image", + content: "https://celenium.io/img/seo/gas.png", + }, ], }) diff --git a/pages/stats/[metric].vue b/pages/stats/[metric].vue index 8b1c84b3..62fbaec9 100644 --- a/pages/stats/[metric].vue +++ b/pages/stats/[metric].vue @@ -7,6 +7,7 @@ import { getSeriesByPage, STATS_PERIODS, STATS_TIMEFRAMES } from "@/services/con import BarChart from "@/components/modules/stats/BarChart.vue" import LineChart from "@/components/modules/stats/LineChart.vue" import SquareSizeChart from "@/components/modules/stats/SquareSizeChart.vue" +import TimelineSlider from "@/components/modules/stats/TimelineSlider.vue" /** Services */ import { getStartChainDate } from "@/services/config" @@ -42,16 +43,16 @@ if (!series.value.page) { } const metricName = computed(() => { - if (series.value.page === "tvs") return series.value.title + if (series.value?.page === "tvs") return series.value.title return capitalizeAndReplaceUnderscore(series.value?.page) }) -// defineOgImage({ -// title: "Rollup", -// rollup: rollup.value, -// component: "RollupImage", -// cacheKey: `${rollup.value?.name}`, -// }) +defineOgImage({ + title: "Statistics", + series: series.value, + component: "StatsMetricImage", + cacheKey: `${series.value?.page}`, +}) useHead({ title: `Celestia ${metricName.value} Statistics - Celestia Explorer`, @@ -97,23 +98,14 @@ useHead({ ], }) -const periods = ref(STATS_PERIODS) -const selectedPeriod = ref(periods.value[2]) +const selectedPeriod = ref({}) -const selectedTimeframe = ref(STATS_TIMEFRAMES.find(tf => tf.timeframe === (series.value.name === "tvs" ? "day" : selectedPeriod.value.timeframe))) +const selectedTimeframe = ref(STATS_TIMEFRAMES.find((tf) => tf.timeframe === "day")) const timeframes = computed(() => { - let res = [] - - for (const tf of STATS_TIMEFRAMES) { - const pointCount = Math.floor(DateTime.fromSeconds(filters.to).diff(DateTime.fromSeconds(filters.from), `${tf.timeframe}s`)[`${tf.timeframe}s`]) + 1 - - if (pointCount > 1 && pointCount < 100) { - res.push(tf) - } - } + let res = [...STATS_TIMEFRAMES] if (series.value.name === "tvs") { - res = res.filter(tf => tf.timeframe === "day" || tf.timeframe === "month") + res = res.filter((tf) => tf.timeframe === "day" || tf.timeframe === "month") } return res @@ -123,168 +115,121 @@ const timeframesStyles = computed(() => { if (!len) return { background: "var(--op-5)" } const segment = 100 / len - const index = timeframes.value.findIndex(tf => tf.timeframe === selectedTimeframe.value.timeframe) + const index = timeframes.value.findIndex((tf) => tf.timeframe === selectedTimeframe.value.timeframe) const start = segment * index const end = start + segment - - return { background: `linear-gradient(to right, transparent ${start}%, var(--op-5) ${start}%, var(--op-5) ${end}%, transparent ${end}%)` } + + return { + background: `linear-gradient(to right, transparent ${start}%, var(--op-5) ${start}%, var(--op-5) ${end}%, transparent ${end}%)`, + } }) const currentData = ref([]) -const prevData = ref([]) + +const allData = ref([]) +const loadedAllData = ref(false) +const currentChartName = ref(null) const chartView = ref("line") -const loadPrevData = ref(true) -const loadLastValue = ref(true) + const updateUserSettings = () => { settingsStore.chart = { ...settingsStore.chart, view: chartView.value, - loadPrevData: loadPrevData.value, - loadLastValue: loadLastValue.value, } } const filters = reactive({}) -const setDefaultFilters = () => { - filters.timeframe = selectedPeriod.value.timeframe - filters.periodValue = selectedPeriod.value.value - filters.from = parseInt(DateTime.now().startOf('day').minus({ - days: selectedPeriod.value.timeframe === "day" ? selectedPeriod.value.value - 1 : 0, - hours: selectedPeriod.value.timeframe === "hour" ? selectedPeriod.value.value : 0, - }).ts / 1_000) - filters.to = parseInt(DateTime.now().endOf('day').ts / 1_000) -} - -setDefaultFilters() - const handleChangeChartView = () => { - if (chartView.value === 'line') { - chartView.value = 'bar' + if (chartView.value === "line") { + chartView.value = "bar" } else { - chartView.value = 'line' + chartView.value = "line" } } const isLoading = ref(false) -const fetchData = async (from, to) => { - let data = [] + +const fetchData = async () => { + loadedAllData.value = false + let data = [] + if (series.value.name === "tvs") { - data = (await fetchTVS({ - period: selectedTimeframe.value.timeframe, - from: from - ? from - : loadPrevData.value - ? parseInt(DateTime.fromSeconds(filters.from).minus({ - hours: filters.timeframe === "hour" ? filters.periodValue : 0, - days: filters.timeframe === "day" ? filters.periodValue : 0, - weeks: filters.timeframe === "week" ? filters.periodValue : 0, - months: filters.timeframe === "month" ? filters.periodValue : 0, - }).ts / 1_000) - : filters.from, - to: to ? to : filters.to - })).map(v => { return { time: v.time, value: v.close } }) - } else if (series.value.aggregate !== 'cumulative') { - data = (await fetchSeries({ + data = ( + await fetchTVS({ + period: selectedTimeframe.value.timeframe, + }) + ).map((v) => { + return { time: v.time, value: v.close } + }) + } else if (series.value.aggregate !== "cumulative") { + let from = filters?.to ? DateTime.fromSeconds(+filters?.to) : DateTime.now() + data = await fetchSeries({ table: series.value.name, period: selectedTimeframe.value.timeframe, - from: from - ? from - : loadPrevData.value - ? parseInt(DateTime.fromSeconds(filters.from).minus({ - hours: filters.timeframe === "hour" ? filters.periodValue : 0, - days: filters.timeframe === "day" ? filters.periodValue : 0, - weeks: filters.timeframe === "week" ? filters.periodValue : 0, - }).ts / 1_000) - : filters.from, - to: to ? to : filters.to - })) + from: selectedTimeframe.value.timeframe === "hour" + ? parseInt(from.minus({ days: 7 }).ts / 1_000) + : null + }) } else { - data = (await fetchSeriesCumulative({ - name: series.value.name, - period: selectedTimeframe.value.timeframe, - from: loadPrevData.value ? parseInt(DateTime.fromSeconds(filters.from).minus({ - hours: filters.timeframe === "hour" ? filters.periodValue : 0, - days: filters.timeframe === "day" ? filters.periodValue : 0, - weeks: filters.timeframe === "week" ? filters.periodValue : 0, - }).ts / 1_000) : filters.from, - to: filters.to - })).reverse() + data = ( + await fetchSeriesCumulative({ + name: series.value.name, + period: selectedTimeframe.value.timeframe, + }) + ).reverse() } - return data + allData.value = data.map((d) => ({ ...d, date: new Date(d.time), timestamp: new Date(d.time).getTime() / 1_000 })) + + currentChartName.value = series.value.name + loadedAllData.value = true + return allData.value } + const getData = async () => { - isLoading.value = true - - let data = await fetchData() - if (data.length) { - if (loadPrevData.value) { - if (selectedTimeframe.value.timeframe !== filters.timeframe) { - if (data.length % 2 > 0) { - const from = parseInt(DateTime.fromISO(data[data.length - 1].time) - .minus({ - hours: selectedTimeframe.value.timeframe === "hour" ? 1 : 0, - days: selectedTimeframe.value.timeframe === "day" ? 1 : 0, - weeks: selectedTimeframe.value.timeframe === "week" ? 1 : 0, - months: selectedTimeframe.value.timeframe === "month" ? 1 : 0 - }).ts / 1_000 - ) - const to = parseInt(DateTime.fromISO(data[data.length - 1].time).ts / 1_000) - let addData = await fetchData(from, to) - if (!addData.length) { - addData = [{ - date: DateTime.fromISO(data[data.length - 1].time) - .minus({ - hours: filters.timeframe === "hour" ? 1 : 0, - days: filters.timeframe === "day" ? 1 : 0, - weeks: filters.timeframe === "week" ? 1 : 0, - }) - .toJSDate(), - value: 0, - }] - } - data.push(addData[0]) - } - - currentData.value = data.slice(0, data.length / 2).map((s) => ({ date: DateTime.fromISO(s.time).toJSDate(), value: parseFloat(s.value) })).reverse() - prevData.value = data.slice(data.length / 2, data.length).map((s) => ({ date: DateTime.fromISO(s.time).toJSDate(), value: parseFloat(s.value) })).reverse() - } else { - currentData.value = data.slice(0, filters.periodValue).map((s) => ({ date: DateTime.fromISO(s.time).toJSDate(), value: parseFloat(s.value) })).reverse() - prevData.value = data.slice(filters.periodValue, data.length).map((s) => ({ date: DateTime.fromISO(s.time).toJSDate(), value: parseFloat(s.value) })).reverse() - while (prevData.value.length < currentData.value.length) { - prevData.value.unshift({ - date: DateTime.fromJSDate(prevData.value[0]?.date) - .minus({ - hours: filters.timeframe === "hour" ? 1 : 0, - days: filters.timeframe === "day" ? 1 : 0, - weeks: filters.timeframe === "week" ? 1 : 0, - }) - .toJSDate(), - value: 0 - }) - } - } - } else { - // currentData.value = data.slice(0, filters.periodValue).map((s) => ({ date: DateTime.fromISO(s.time).toJSDate(), value: parseFloat(s.value) })).reverse() - currentData.value = data.map((s) => ({ date: DateTime.fromISO(s.time).toJSDate(), value: parseFloat(s.value) })).reverse() - prevData.value = [] - } - } - - series.value.currentData = loadLastValue.value ? currentData.value : [...currentData.value.slice(0, -1)] - series.value.prevData = (loadLastValue.value ? prevData.value : (prevData.value.length ? [...prevData.value.slice(0, -1)] : prevData.value)).slice(-series.value.currentData.length) - series.value.timeframe = filters.timeframe + isLoading.value = true - isLoading.value = false + let data = [] + + const isSameRequest = currentChartName.value === series.value.name && loadedAllData.value && allData.value.length > 0 + + if (!isSameRequest) { + await fetchData() + } + + data = allData.value + + if (data.length > 0 && !filters.from && !filters.to) { + const firstDate = new Date(data[data.length - 1].time) + const lastDate = new Date(data[0].time) + + filters.from = Math.floor(firstDate.getTime() / 1000) + filters.to = Math.floor(lastDate.getTime() / 1000) + } + + currentData.value = data + .filter((d) => { + const time = new Date(d.time).getTime() / 1_000 + return time >= filters.from && time <= filters.to + }) + .map((s) => ({ date: DateTime.fromISO(s.time).toJSDate(), value: parseFloat(s.value) })) + .reverse() + + series.value.currentData = [...currentData.value] + + filters.timeframe = selectedTimeframe.value + series.value.timeframe = filters.timeframe + isLoading.value = false } -if (series.value.name !== 'square_size') { +if (series.value.name !== "square_size") { await getData() } const isOpen = ref(false) + const handleOpen = () => { isOpen.value = true } @@ -292,6 +237,16 @@ const handleClose = () => { isOpen.value = false } +const handleDatePickerUpdate = async (event) => { + selectedTimeframe.value = STATS_TIMEFRAMES.find((tf) => tf.timeframe === "day") + await handleUpdateDate(event) +} + +const handleTimelineUpdate = async (event) => { + await handleUpdateDate(event) + // selectedPeriod.value = {} +} + const handleUpdateDate = async (event) => { isLoading.value = true @@ -299,62 +254,43 @@ const handleUpdateDate = async (event) => { let from = event.from let to = event.to - let daysDiff = Math.round(DateTime.fromSeconds(to).diff(DateTime.fromSeconds(from), 'days').days) - if (series.value.name === "tvs") { - if (daysDiff < 50) { - filters.timeframe = 'day' - filters.periodValue = daysDiff - } else { - filters.timeframe = 'month' - filters.periodValue = Math.ceil(daysDiff / 30) - } - } else { - if (daysDiff < 7) { - filters.timeframe = 'hour' - filters.periodValue = Math.round(DateTime.fromSeconds(to).diff(DateTime.fromSeconds(from), 'hours').hours) - } else if (daysDiff < 50) { - filters.timeframe = 'day' - filters.periodValue = daysDiff - } else { - filters.timeframe = 'week' - filters.periodValue = Math.ceil(daysDiff / 7) - } + from = DateTime.fromSeconds(from).startOf("day").toSeconds() + to = Math.floor(DateTime.fromSeconds(to).endOf("day").toSeconds()) + + if (filters.from === from && filters.to === to) { + isLoading.value = false + return } + + filters.from = from + filters.to = to - if (filters.timeframe === 'hour') { - const hoursDiff = Math.round(DateTime.fromSeconds(Math.min(to, DateTime.now().ts / 1_000)).diff(DateTime.fromSeconds(from), 'hours').hours) - if (hoursDiff < filters.periodValue) { - from = parseInt( - DateTime.fromSeconds(Math.min(to, DateTime.now().ts / 1_000)) - .minus({ hours: filters.periodValue }) - .ts / 1_000 - ) + if (event.source !== 'timeline') { + if (Math.abs(DateTime.fromSeconds(from).diff(DateTime.fromSeconds(to), 'days').days) < 8) { + selectedTimeframe.value = STATS_TIMEFRAMES.find((tf) => tf.timeframe === "hour") } } - - filters.from = from - filters.to = to - selectedTimeframe.value = timeframes.value.find(tf => tf.timeframe === filters.timeframe) - - await getData() - } else if (event.clear) { - setDefaultFilters() await getData() } + + isLoading.value = false } const handleTimeframeUpdate = (tf) => { selectedTimeframe.value = tf + filters.from = null + filters.to = null } const handleCSVDownload = async () => { - let data = [...series.value.currentData, ...series.value.prevData] - let csvHeaders = 'timestamp,value\n' - let csvRow = data.map(el => `${DateTime.fromJSDate(el.date).ts},${el.value}`).join('\n') + let data = [...series.value.currentData] + + let csvHeaders = "timestamp,value\n" + let csvRow = data.map((el) => `${DateTime.fromJSDate(el.date).ts},${el.value}`).join("\n") await exportToCSV(csvHeaders + csvRow, `${series.value.name}-${filters.from}-${filters.to}`) - + notificationsStore.create({ notification: { type: "success", @@ -366,7 +302,7 @@ const handleCSVDownload = async () => { } const handlePNGDownload = async () => { - const svgElement = document.getElementById('chart') + const svgElement = document.getElementById("chart") await exportSVGToPNG(svgElement, `${series.value.name}-${filters.from}-${filters.to}-${chartView.value}`) @@ -387,63 +323,33 @@ const handleOpenChartModal = () => { modalsStore.open("chart") } -watch( - () => loadLastValue.value, - () => { - if (loadLastValue.value) { - series.value.currentData = currentData.value - series.value.prevData = loadPrevData.value ? prevData.value : [] - } else { - series.value.currentData = [...currentData.value.slice(0, -1)] - if (prevData.value.length && loadPrevData.value) { - series.value.prevData = [...prevData.value.slice(0, -1)] - } - } - } -) - -watch( - () => loadPrevData.value, - async () => { - if (loadPrevData.value) { - if (prevData.value.length) { - series.value.currentData = loadLastValue.value ? currentData.value : [...currentData.value.slice(0, -1)] - series.value.prevData = loadLastValue.value ? prevData.value : [...prevData.value.slice(0, -1)] - } else { - await getData() - } - } else { - series.value.prevData = [] - } - }, -) +const isInternalUpdate = ref(false) watch( () => selectedTimeframe.value, async () => { - if (!isLoading.value) { + if (!isLoading.value && !isInternalUpdate.value) { + allData.value = [] await getData() } - } + }, ) watch( - () => [chartView.value, loadLastValue.value, loadPrevData.value], + () => [chartView.value], () => { updateUserSettings() - } + }, ) onBeforeMount(() => { const settings = JSON.parse(localStorage.getItem("settings")) chartView.value = settings?.chart?.view || "line" - loadPrevData.value = settings?.chart?.loadPrevData - loadLastValue.value = settings?.chart?.loadLastValue }) - + { - + @@ -485,7 +391,9 @@ onBeforeMount(() => { gap="12" :class="$style.chart_selector" :style="{ - background: `linear-gradient(to ${chartView === 'line' ? 'right' : 'left'}, var(--op-5) 50%, transparent 50%)`, + background: `linear-gradient(to ${ + chartView === 'line' ? 'right' : 'left' + }, var(--op-5) 50%, transparent 50%)`, }" > { - - Previous period - - - - - Show last value - - - Group by - + { - - + + + -