-
Difficulty adjustment
-
-
-
-
-
0">+{{ 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' }} %
+
+
+
+
+
+
+
+
+
+
+
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,