From d405334109243d30bde16cc5c332c94a4553f60f Mon Sep 17 00:00:00 2001 From: Miguel Medeiros Date: Sat, 17 Jul 2021 08:58:16 -0300 Subject: [PATCH] UI/UX - New component for difficult adjustment. (#602) * Add next difficulty blocks. Add next difficulty target date. Add next difficulty total progress. Add ajustment difficulty avg min per block. * Fix typo. * Trigger difficulty calculation every 5 seconds. * Add rxjs timer to difficultyEpoch. * Fix pipe. * Fix small bar position. * Change i18n strings. * Fix typo. * Add time-until component. * Speed up difficultyEpoch timer to 1000 ms. * Fix values to 2 decimal places. * Add title to fee and difficulty adjustment cards. * Add title outside the card. * Fix title to center position. * Add other titles. * Add new transalations strings. Refactor time span component. * Fix difficulty adjustment i18n string. Fix duplicated i18n strings. --- frontend/src/app/app.module.ts | 4 +- .../components/footer/footer.component.html | 4 +- .../mempool-blocks.component.html | 2 +- .../time-since/time-since.component.ts | 46 +++---- .../time-span/time-span.component.ts | 107 ++++++++++++++++ .../time-until/time-until.component.ts | 108 +++++++++++++++++ .../components/timespan/timespan.component.ts | 45 ------- .../transaction/transaction.component.html | 6 +- .../app/dashboard/dashboard.component.html | 70 +++++++++-- .../app/dashboard/dashboard.component.scss | 114 +++++++++++++++++- .../src/app/dashboard/dashboard.component.ts | 95 ++++++++++----- frontend/src/app/shared/i18n/dates.ts | 22 ++++ frontend/src/app/shared/shared.module.ts | 3 + 13 files changed, 509 insertions(+), 117 deletions(-) create mode 100644 frontend/src/app/components/time-span/time-span.component.ts create mode 100644 frontend/src/app/components/time-until/time-until.component.ts delete mode 100644 frontend/src/app/components/timespan/timespan.component.ts create mode 100644 frontend/src/app/shared/i18n/dates.ts diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 1d246561c..09ea8c047 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -32,7 +32,7 @@ import { FooterComponent } from './components/footer/footer.component'; import { AudioService } from './services/audio.service'; import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component'; import { FeeDistributionGraphComponent } from './components/fee-distribution-graph/fee-distribution-graph.component'; -import { TimespanComponent } from './components/timespan/timespan.component'; +import { TimeSpanComponent } from './components/time-span/time-span.component'; import { SeoService } from './services/seo.service'; import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component'; import { AssetComponent } from './components/asset/asset.component'; @@ -71,7 +71,7 @@ import { SponsorComponent } from './components/sponsor/sponsor.component'; AmountComponent, LatestBlocksComponent, SearchFormComponent, - TimespanComponent, + TimeSpanComponent, AddressLabelsComponent, MempoolBlocksComponent, ChartistComponent, diff --git a/frontend/src/app/components/footer/footer.component.html b/frontend/src/app/components/footer/footer.component.html index 9584ec2a2..b8a73288e 100644 --- a/frontend/src/app/components/footer/footer.component.html +++ b/frontend/src/app/components/footer/footer.component.html @@ -19,8 +19,8 @@
Mempool size:
()
- {{ i }} block - {{ i }} blocks + {{ i }} blocks + {{ i }} block
diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html index ff18f198e..8e3655724 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html @@ -27,7 +27,7 @@
() - {{ i }} blocks + {{ i }} blocks
diff --git a/frontend/src/app/components/time-since/time-since.component.ts b/frontend/src/app/components/time-since/time-since.component.ts index 377e4b91c..e87ab9ab6 100644 --- a/frontend/src/app/components/time-since/time-since.component.ts +++ b/frontend/src/app/components/time-since/time-since.component.ts @@ -1,5 +1,6 @@ -import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnChanges, PLATFORM_ID, Inject } from '@angular/core'; +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnChanges } from '@angular/core'; import { StateService } from 'src/app/services/state.service'; +import { dates } from 'src/app/shared/i18n/dates'; @Component({ selector: 'app-time-since', @@ -53,48 +54,49 @@ export class TimeSinceComponent implements OnInit, OnChanges, OnDestroy { calculate() { const seconds = Math.floor((+new Date() - +new Date(this.time * 1000)) / 1000); if (seconds < 60) { - return $localize`:@@time-since.just-now:Just now`; + return $localize`:@@date-base.just-now:Just now`; } let counter; for (const i in this.intervals) { if (this.intervals.hasOwnProperty(i)) { counter = Math.floor(seconds / this.intervals[i]); + const dateStrings = dates(counter); if (counter > 0) { if (counter === 1) { - switch (i) { // singular (1 day ago) - case 'year': return $localize`:@@time-since.year.ago:${counter}:INTERPOLATION: year ago`; break; - case 'month': return $localize`:@@time-since.month.ago:${counter}:INTERPOLATION: month ago`; break; - case 'week': return $localize`:@@time-since.week.ago:${counter}:INTERPOLATION: week ago`; break; - case 'day': return $localize`:@@time-since.day.ago:${counter}:INTERPOLATION: day ago`; break; - case 'hour': return $localize`:@@time-since.hour.ago:${counter}:INTERPOLATION: hour ago`; break; + switch (i) { // singular (1 day) + case 'year': return $localize`:@@time-since:${dateStrings.i18nYear}:DATE: ago`; break; + case 'month': return $localize`:@@time-since:${dateStrings.i18nMonth}:DATE: ago`; break; + case 'week': return $localize`:@@time-since:${dateStrings.i18nWeek}:DATE: ago`; break; + case 'day': return $localize`:@@time-since:${dateStrings.i18nDay}:DATE: ago`; break; + case 'hour': return $localize`:@@time-since:${dateStrings.i18nHour}:DATE: ago`; break; case 'minute': if (document.body.clientWidth < 768) { - return $localize`:@@time-since.min.ago:${counter}:INTERPOLATION: min ago`; + return $localize`:@@time-since:${dateStrings.i18nMin}:DATE: ago`; } - return $localize`:@@time-since.minute.ago:${counter}:INTERPOLATION: minute ago`; + return $localize`:@@time-since:${dateStrings.i18nMinute}:DATE: ago`; case 'second': if (document.body.clientWidth < 768) { - return $localize`:@@time-since.sec.ago:${counter}:INTERPOLATION: sec ago`; + return $localize`:@@time-since:${dateStrings.i18nSec}:DATE: ago`; } - return $localize`:@@time-since.second.ago:${counter}:INTERPOLATION: second ago`; + return $localize`:@@time-since:${dateStrings.i18nSecond}:DATE: ago`; } } else { - switch (i) { // plural (2 days ago) - case 'year': return $localize`:@@time-since.years.ago:${counter}:INTERPOLATION: years ago`; break; - case 'month': return $localize`:@@time-since.months.ago:${counter}:INTERPOLATION: months ago`; break; - case 'week': return $localize`:@@time-since.weeks.ago:${counter}:INTERPOLATION: weeks ago`; break; - case 'day': return $localize`:@@time-since.days.ago:${counter}:INTERPOLATION: days ago`; break; - case 'hour': return $localize`:@@time-since.hours.ago:${counter}:INTERPOLATION: hours ago`; break; + switch (i) { // plural (2 days) + case 'year': return $localize`:@@time-since:${dateStrings.i18nYears}:DATE: ago`; break; + case 'month': return $localize`:@@time-since:${dateStrings.i18nMonths}:DATE: ago`; break; + case 'week': return $localize`:@@time-since:${dateStrings.i18nWeeks}:DATE: ago`; break; + case 'day': return $localize`:@@time-since:${dateStrings.i18nDays}:DATE: ago`; break; + case 'hour': return $localize`:@@time-since:${dateStrings.i18nHours}:DATE: ago`; break; case 'minute': if (document.body.clientWidth < 768) { - return $localize`:@@time-since.mins.ago:${counter}:INTERPOLATION: mins ago`; + return $localize`:@@time-since:${dateStrings.i18nMins}:DATE: ago`; } - return $localize`:@@time-since.minutes.ago:${counter}:INTERPOLATION: minutes ago`; + return $localize`:@@time-since:${dateStrings.i18nMinutes}:DATE: ago`; case 'second': if (document.body.clientWidth < 768) { - return $localize`:@@time-since.secs.ago:${counter}:INTERPOLATION: secs ago`; + return $localize`:@@time-since:${dateStrings.i18nSecs}:DATE: ago`; } - return $localize`:@@time-since.seconds.ago:${counter}:INTERPOLATION: seconds ago`; + return $localize`:@@time-since:${dateStrings.i18nSeconds}:DATE: ago`; } } } diff --git a/frontend/src/app/components/time-span/time-span.component.ts b/frontend/src/app/components/time-span/time-span.component.ts new file mode 100644 index 000000000..f5a0ca99f --- /dev/null +++ b/frontend/src/app/components/time-span/time-span.component.ts @@ -0,0 +1,107 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnChanges } from '@angular/core'; +import { StateService } from 'src/app/services/state.service'; +import { dates } from 'src/app/shared/i18n/dates'; + +@Component({ + selector: 'app-time-span', + template: `{{ text }}`, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TimeSpanComponent implements OnInit, OnChanges, OnDestroy { + interval: number; + text: string; + intervals = {}; + + @Input() time: number; + @Input() fastRender = false; + + constructor( + private ref: ChangeDetectorRef, + private stateService: StateService, + ) { + this.intervals = { + year: 31536000, + month: 2592000, + week: 604800, + day: 86400, + hour: 3600, + minute: 60, + second: 1 + }; + } + + ngOnInit() { + if (!this.stateService.isBrowser) { + this.text = this.calculate(); + this.ref.markForCheck(); + return; + } + this.interval = window.setInterval(() => { + this.text = this.calculate(); + this.ref.markForCheck(); + }, 1000 * (this.fastRender ? 1 : 60)); + } + + ngOnChanges() { + this.text = this.calculate(); + this.ref.markForCheck(); + } + + ngOnDestroy() { + clearInterval(this.interval); + } + + calculate() { + const seconds = Math.floor((+new Date() - +new Date(this.time * 1000)) / 1000); + if (seconds < 60) { + return $localize`:@@date-base.just-now:Just now`; + } + let counter; + for (const i in this.intervals) { + if (this.intervals.hasOwnProperty(i)) { + counter = Math.floor(seconds / this.intervals[i]); + const dateStrings = dates(counter); + if (counter > 0) { + if (counter === 1) { + switch (i) { // singular (1 day) + case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYear}:DATE:`; break; + case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonth}:DATE:`; break; + case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeek}:DATE:`; break; + case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDay}:DATE:`; break; + case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHour}:DATE:`; break; + case 'minute': + if (document.body.clientWidth < 768) { + return $localize`:@@time-span:After ${dateStrings.i18nMin}:DATE:`; + } + return $localize`:@@time-span:After ${dateStrings.i18nMinute}:DATE:`; + case 'second': + if (document.body.clientWidth < 768) { + return $localize`:@@time-span:After ${dateStrings.i18nSec}:DATE:`; + } + return $localize`:@@time-span:After ${dateStrings.i18nSecond}:DATE:`; + } + } else { + switch (i) { // plural (2 days) + case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYears}:DATE:`; break; + case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonths}:DATE:`; break; + case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeeks}:DATE:`; break; + case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDays}:DATE:`; break; + case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHours}:DATE:`; break; + case 'minute': + if (document.body.clientWidth < 768) { + return $localize`:@@time-span:After ${dateStrings.i18nMins}:DATE:`; + } + return $localize`:@@time-span:After ${dateStrings.i18nMinutes}:DATE:`; + case 'second': + if (document.body.clientWidth < 768) { + return $localize`:@@time-span:After ${dateStrings.i18nSecs}:DATE:`; + } + return $localize`:@@time-span:After ${dateStrings.i18nSeconds}:DATE:`; + } + } + } + } + } + } + +} diff --git a/frontend/src/app/components/time-until/time-until.component.ts b/frontend/src/app/components/time-until/time-until.component.ts new file mode 100644 index 000000000..b85d909cd --- /dev/null +++ b/frontend/src/app/components/time-until/time-until.component.ts @@ -0,0 +1,108 @@ +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnChanges } from '@angular/core'; +import { StateService } from 'src/app/services/state.service'; +import { dates } from 'src/app/shared/i18n/dates'; + +@Component({ + selector: 'app-time-until', + template: `{{ text }}`, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TimeUntilComponent implements OnInit, OnChanges, OnDestroy { + interval: number; + text: string; + intervals = {}; + + @Input() time: number; + @Input() fastRender = false; + + constructor( + private ref: ChangeDetectorRef, + private stateService: StateService, + ) { + this.intervals = { + year: 31536000, + month: 2592000, + week: 604800, + day: 86400, + hour: 3600, + minute: 60, + second: 1 + }; + } + + ngOnInit() { + if (!this.stateService.isBrowser) { + this.text = this.calculate(); + this.ref.markForCheck(); + return; + } + this.interval = window.setInterval(() => { + this.text = this.calculate(); + this.ref.markForCheck(); + }, 1000 * (this.fastRender ? 1 : 60)); + } + + ngOnChanges() { + this.text = this.calculate(); + this.ref.markForCheck(); + } + + ngOnDestroy() { + clearInterval(this.interval); + } + + calculate() { + const seconds = Math.floor((+new Date(this.time) - +new Date()) / 1000); + + if (seconds < 60) { + return $localize`:@@date-base.last-minute:In ~1 min`; + } + let counter; + for (const i in this.intervals) { + if (this.intervals.hasOwnProperty(i)) { + counter = Math.floor(seconds / this.intervals[i]); + const dateStrings = dates(counter); + if (counter > 0) { + if (counter === 1) { + switch (i) { // singular (In ~1 day) + case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYear}:DATE:`; break; + case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonth}:DATE:`; break; + case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeek}:DATE:`; break; + case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDay}:DATE:`; break; + case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHour}:DATE:`; break; + case 'minute': + if (document.body.clientWidth < 768) { + return $localize`:@@time-until:In ~${dateStrings.i18nMin}:DATE:`; + } + return $localize`:@@time-until:In ~${dateStrings.i18nMinute}:DATE:`; + case 'second': + if (document.body.clientWidth < 768) { + return $localize`:@@time-until:In ~${dateStrings.i18nSec}:DATE:`; + } + return $localize`:@@time-until:In ~${dateStrings.i18nSecond}:DATE:`; + } + } else { + switch (i) { // plural (In ~2 days) + case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYears}:DATE:`; break; + case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonths}:DATE:`; break; + case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeeks}:DATE:`; break; + case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDays}:DATE:`; break; + case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHours}:DATE:`; break; + case 'minute': + if (document.body.clientWidth < 768) { + return $localize`:@@time-until:In ~${dateStrings.i18nMins}:DATE:`; + } + return $localize`:@@time-until:In ~${dateStrings.i18nMinutes}:DATE:`; + case 'second': + if (document.body.clientWidth < 768) { + return $localize`:@@time-until:In ~${dateStrings.i18nSecs}:DATE:`; + } + return $localize`:@@time-until:In ~${dateStrings.i18nSeconds}:DATE:`; + } + } + } + } + } + } + +} diff --git a/frontend/src/app/components/timespan/timespan.component.ts b/frontend/src/app/components/timespan/timespan.component.ts deleted file mode 100644 index 4edba3df0..000000000 --- a/frontend/src/app/components/timespan/timespan.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Component, ChangeDetectionStrategy, Input, OnChanges } from '@angular/core'; - -@Component({ - selector: 'app-timespan', - template: `{{ text }}`, - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TimespanComponent implements OnChanges { - @Input() time: number; - text: string; - - constructor() { } - - ngOnChanges() { - const seconds = this.time; - if (seconds < 60) { - this.text = '< 1 minute'; - return; - } - const intervals = { - year: 31536000, - month: 2592000, - week: 604800, - day: 86400, - hour: 3600, - minute: 60, - second: 1 - }; - let counter; - for (const i in intervals) { - if (intervals.hasOwnProperty(i)) { - counter = Math.floor(seconds / intervals[i]); - if (counter > 0) { - if (counter === 1) { - this.text = counter + ' ' + i; // singular (1 day ago) - break; - } else { - this.text = counter + ' ' + i + 's'; // plural (2 days ago) - break; - } - } - } - } - } -} diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index d2080498b..844ed0c33 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -65,7 +65,7 @@ Confirmed - After + @@ -321,8 +321,8 @@ In ~{{ i }} minute -{{ i }} block -{{ i }} blocks +{{ i }} block +{{ i }} blocks diff --git a/frontend/src/app/dashboard/dashboard.component.html b/frontend/src/app/dashboard/dashboard.component.html index ca8104489..59294e53a 100644 --- a/frontend/src/app/dashboard/dashboard.component.html +++ b/frontend/src/app/dashboard/dashboard.component.html @@ -1,9 +1,9 @@
-
-
+
+
Fee Estimates
@@ -29,7 +29,8 @@
-
+
+
Fee Estimates
@@ -198,14 +199,61 @@ -
-
-
Difficulty adjustment
-
-
 
-
-
-
+{{ epochData.change | number: '1.0-2' }}%
+
Difficulty Adjustment
+
+
+
+
+
+
Remaining
+
+ + {{ i }} blocks + {{ i }} block +
+
+
+
+
Estimate
+
{{ epochData.change | number: '1.2-2' }} %
+
~{{ epochData.timeAvg }} mins per block
+
+
+
Current Period
+
{{ epochData.progress | number: '1.2-2' }} %
+
+
 
+
+
+
+
+
+
+
+ + + + +
+
+
Remaining
+
+
+
+
+
+
+
Estimate
+
+
+
+
+
+
+
Current Period
+
+
+
diff --git a/frontend/src/app/dashboard/dashboard.component.scss b/frontend/src/app/dashboard/dashboard.component.scss index 2d969db58..30f1c4fb9 100644 --- a/frontend/src/app/dashboard/dashboard.component.scss +++ b/frontend/src/app/dashboard/dashboard.component.scss @@ -42,7 +42,7 @@ } .more-padding { - padding: 1.25rem 2rem 1.25rem 2rem; + padding: 18px; } .graph-card { @@ -212,4 +212,116 @@ .terms-of-service { margin-top: 1rem; +} + +.small-bar { + height: 8px; + top: -4px; +} + +.difficulty-adjustment-container { + display: flex; + flex-direction: row; + justify-content: space-around; + height: 76px; + .shared-block { + color: #ffffff66; + font-size: 12px; + } + .item { + max-width: 130px; + padding: 0 5px; + width: 100%; + &:nth-child(1){ + display: none; + @media (min-width: 485px) { + display: table-cell; + } + @media (min-width: 768px) { + display: none; + } + @media (min-width: 992px) { + display: table-cell; + } + } + } + .card-text { + font-size: 22px; + margin-top: -9px; + position: relative; + } +} + + +.difficulty-skeleton { + display: flex; + justify-content: space-between; + @media (min-width: 376px) { + flex-direction: row; + } + .item { + max-width: 150px; + margin: 0; + width: -webkit-fill-available; + @media (min-width: 376px) { + margin: 0 auto 0px; + } + &:first-child{ + display: none; + @media (min-width: 485px) { + display: block; + } + @media (min-width: 768px) { + display: none; + } + @media (min-width: 992px) { + display: block; + } + } + &:last-child { + margin-bottom: 0; + } + } +} + +.loading-container{ + min-height: 76px; +} + +.card-text { + .skeleton-loader { + width: 100%; + display: block; + margin: 7px auto; + &:first-child { + max-width: 80px; + } + &:last-child { + max-width: 110px; + } + } +} + +.main-title { + position: relative; + color: #ffffff91; + margin-top: -13px; + font-size: 10px; + text-transform: uppercase; + font-weight: 500; + text-align: center; + padding-bottom: 3px; +} +.card-wrapper { + .card { + height: auto !important; + } + .card-body { + display: flex; + flex: inherit; + text-align: center; + flex-direction: column; + justify-content: space-around; + padding: 22px 20px; + } } \ No newline at end of file diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts index 8000ba912..92fadecbf 100644 --- a/frontend/src/app/dashboard/dashboard.component.ts +++ b/frontend/src/app/dashboard/dashboard.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@angular/core'; -import { combineLatest, merge, Observable, of } from 'rxjs'; +import { combineLatest, merge, Observable, of, timer } from 'rxjs'; import { filter, map, scan, share, switchMap, tap } from 'rxjs/operators'; import { Block } from '../interfaces/electrs.interface'; import { OptimizedMempoolStats } from '../interfaces/node-api.interface'; @@ -22,6 +22,12 @@ interface EpochProgress { green: string; red: string; change: number; + progress: string; + remainingBlocks: number; + newDifficultyHeight: number; + colorAdjustments: string; + timeAvg: string; + remainingTime: number; } interface MempoolInfoData { @@ -108,38 +114,67 @@ export class DashboardComponent implements OnInit { }) ); - this.difficultyEpoch$ = combineLatest([ - this.stateService.blocks$.pipe(map(([block]) => block)), - this.stateService.lastDifficultyAdjustment$ - ]) - .pipe( - map(([block, DATime]) => { - const now = new Date().getTime() / 1000; - const diff = now - DATime; - const blocksInEpoch = block.height % 2016; - const estimatedBlocks = Math.round(diff / 60 / 10); - const difficultyChange = (600 / (diff / blocksInEpoch ) - 1) * 100; + this.difficultyEpoch$ = timer(0, 1000) + .pipe( + switchMap(() => combineLatest([ + this.stateService.blocks$.pipe(map(([block]) => block)), + this.stateService.lastDifficultyAdjustment$ + ])), + map(([block, DATime]) => { + const now = new Date().getTime() / 1000; + const diff = now - DATime; + const blocksInEpoch = block.height % 2016; + const estimatedBlocks = Math.round(diff / 60 / 10); + let difficultyChange = 0; + if (blocksInEpoch > 0) { + difficultyChange = (600 / (diff / blocksInEpoch ) - 1) * 100; + } - let base = 0; - let green = 0; - let red = 0; + let base = 0; + let green = 0; + let red = 0; - if (blocksInEpoch >= estimatedBlocks) { - base = estimatedBlocks / 2016 * 100; - green = (blocksInEpoch - estimatedBlocks) / 2016 * 100; - } else { - base = blocksInEpoch / 2016 * 100; - red = Math.min((estimatedBlocks - blocksInEpoch) / 2016 * 100, 100 - base); - } + if (blocksInEpoch >= estimatedBlocks) { + base = estimatedBlocks / 2016 * 100; + green = (blocksInEpoch - estimatedBlocks) / 2016 * 100; + } else { + base = blocksInEpoch / 2016 * 100; + red = Math.min((estimatedBlocks - blocksInEpoch) / 2016 * 100, 100 - base); + } - return { - base: base + '%', - green: green + '%', - red: red + '%', - change: difficultyChange, - }; - }) - ); + let colorAdjustments = '#dc3545'; + if (difficultyChange >= 0) { + colorAdjustments = '#299435'; + } + + const timeAvgDiff = difficultyChange * 0.1; + + let timeAvgMins = 10; + if (timeAvgDiff > 0 ){ + timeAvgMins -= Math.abs(timeAvgDiff); + } else { + timeAvgMins += Math.abs(timeAvgDiff); + } + const remainingBlocks = 2016 - blocksInEpoch; + const nowMilliseconds = now * 1000; + const timeAvgMilliseconds = timeAvgMins * 60 * 1000; + const remainingBlocsMilliseconds = remainingBlocks * timeAvgMilliseconds; + + return { + base: base + '%', + green: green + '%', + red: red + '%', + change: difficultyChange, + progress: base.toFixed(2), + remainingBlocks, + timeAvg: timeAvgMins.toFixed(0), + colorAdjustments, + blocksInEpoch, + newDifficultyHeight: block.height + remainingBlocks, + remainingTime: remainingBlocsMilliseconds + nowMilliseconds + }; + }) + ); this.mempoolBlocksData$ = this.stateService.mempoolBlocks$ .pipe( diff --git a/frontend/src/app/shared/i18n/dates.ts b/frontend/src/app/shared/i18n/dates.ts new file mode 100644 index 000000000..24e8272c6 --- /dev/null +++ b/frontend/src/app/shared/i18n/dates.ts @@ -0,0 +1,22 @@ +export const dates = (counter: number) => { + return { + i18nYear: $localize`:@@date-base.year:${counter}:DATE: year`, + i18nYears: $localize`:@@date-base.years:${counter}:DATE: years`, + i18nMonth: $localize`:@@date-base.month:${counter}:DATE: month`, + i18nMonths: $localize`:@@date-base.months:${counter}:DATE: months`, + i18nWeek: $localize`:@@date-base.week:${counter}:DATE: week`, + i18nWeeks: $localize`:@@date-base.weeks:${counter}:DATE: weeks`, + i18nDay: $localize`:@@date-base.day:${counter}:DATE: day`, + i18nDays: $localize`:@@date-base.days:${counter}:DATE: days`, + i18nHour: $localize`:@@date-base.hour:${counter}:DATE: hour`, + i18nHours: $localize`:@@date-base.hours:${counter}:DATE: hours`, + i18nMinute: $localize`:@@date-base.minute:${counter}:DATE: minute`, + i18nMinutes: $localize`:@@date-base.minutes:${counter}:DATE: minutes`, + i18nMin: $localize`:@@date-base.min:${counter}:DATE: min`, + i18nMins: $localize`:@@date-base.mins:${counter}:DATE: mins`, + i18nSecond: $localize`:@@date-base.second:${counter}:DATE: second`, + i18nSeconds: $localize`:@@date-base.seconds:${counter}:DATE: seconds`, + i18nSec: $localize`:@@date-base.sec:${counter}:DATE: sec`, + i18nSecs: $localize`:@@date-base.secs:${counter}:DATE: secs`, + } +} \ No newline at end of file diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 61c57a92d..0d7d9bc32 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -11,6 +11,7 @@ import { ScriptpubkeyTypePipe } from './pipes/scriptpubkey-type-pipe/scriptpubke import { BytesPipe } from './pipes/bytes-pipe/bytes.pipe'; import { WuBytesPipe } from './pipes/bytes-pipe/wubytes.pipe'; import { TimeSinceComponent } from '../components/time-since/time-since.component'; +import { TimeUntilComponent } from '../components/time-until/time-until.component'; import { ClipboardComponent } from '../components/clipboard/clipboard.component'; import { QrcodeComponent } from '../components/qrcode/qrcode.component'; import { FiatComponent } from '../fiat/fiat.component'; @@ -25,6 +26,7 @@ import { ColoredPriceDirective } from './directives/colored-price.directive'; declarations: [ ClipboardComponent, TimeSinceComponent, + TimeUntilComponent, QrcodeComponent, FiatComponent, TxFeaturesComponent, @@ -65,6 +67,7 @@ import { ColoredPriceDirective } from './directives/colored-price.directive'; NgbPaginationModule, NgbDropdownModule, TimeSinceComponent, + TimeUntilComponent, ClipboardComponent, QrcodeComponent, FiatComponent,