Redesign difficulty adjustment dashboard widget
This commit is contained in:
		
							parent
							
								
									e1f0bb9901
								
							
						
					
					
						commit
						39051e94e3
					
				@ -23,9 +23,11 @@ describe('Mempool Difficulty Adjustment', () => {
 | 
			
		||||
          remainingBlocks: 1834,
 | 
			
		||||
          remainingTime: 977591692,
 | 
			
		||||
          previousRetarget: 0.6280047707459726,
 | 
			
		||||
          previousTime: 1660820820,
 | 
			
		||||
          nextRetargetHeight: 751968,
 | 
			
		||||
          timeAvg: 533038,
 | 
			
		||||
          timeOffset: 0,
 | 
			
		||||
          expectedBlocks: 161.68833333333333,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      [ // Vector 2 (testnet)
 | 
			
		||||
@ -43,11 +45,13 @@ describe('Mempool Difficulty Adjustment', () => {
 | 
			
		||||
          estimatedRetargetDate: 1661895424692,
 | 
			
		||||
          remainingBlocks: 1834,
 | 
			
		||||
          remainingTime: 977591692,
 | 
			
		||||
          previousTime: 1660820820,
 | 
			
		||||
          previousRetarget: 0.6280047707459726,
 | 
			
		||||
          nextRetargetHeight: 751968,
 | 
			
		||||
          timeAvg: 533038,
 | 
			
		||||
          timeOffset: -667000, // 11 min 7 seconds since last block (testnet only)
 | 
			
		||||
          // If we add time avg to abs(timeOffset) it makes exactly 1200000 ms, or 20 minutes
 | 
			
		||||
          expectedBlocks: 161.68833333333333,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    ] as [[number, number, number, number, string, number], DifficultyAdjustment][];
 | 
			
		||||
 | 
			
		||||
@ -9,9 +9,11 @@ export interface DifficultyAdjustment {
 | 
			
		||||
  remainingBlocks: number;       // Block count
 | 
			
		||||
  remainingTime: number;         // Duration of time in ms
 | 
			
		||||
  previousRetarget: number;      // Percent: -75 to 300
 | 
			
		||||
  previousTime: number;          // Unix time in ms
 | 
			
		||||
  nextRetargetHeight: number;    // Block Height
 | 
			
		||||
  timeAvg: number;               // Duration of time in ms
 | 
			
		||||
  timeOffset: number;            // (Testnet) Time since last block (cap @ 20min) in ms
 | 
			
		||||
  expectedBlocks: number;         // Block count
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function calcDifficultyAdjustment(
 | 
			
		||||
@ -32,12 +34,12 @@ export function calcDifficultyAdjustment(
 | 
			
		||||
  const progressPercent = (blockHeight >= 0) ? blocksInEpoch / EPOCH_BLOCK_LENGTH * 100 : 100;
 | 
			
		||||
  const remainingBlocks = EPOCH_BLOCK_LENGTH - blocksInEpoch;
 | 
			
		||||
  const nextRetargetHeight = (blockHeight >= 0) ? blockHeight + remainingBlocks : 0;
 | 
			
		||||
  const expectedBlocks = diffSeconds / BLOCK_SECONDS_TARGET;
 | 
			
		||||
 | 
			
		||||
  let difficultyChange = 0;
 | 
			
		||||
  let timeAvgSecs = BLOCK_SECONDS_TARGET;
 | 
			
		||||
  let timeAvgSecs = diffSeconds / blocksInEpoch;
 | 
			
		||||
  // Only calculate the estimate once we have 7.2% of blocks in current epoch
 | 
			
		||||
  if (blocksInEpoch >= ESTIMATE_LAG_BLOCKS) {
 | 
			
		||||
    timeAvgSecs = diffSeconds / blocksInEpoch;
 | 
			
		||||
    difficultyChange = (BLOCK_SECONDS_TARGET / timeAvgSecs - 1) * 100;
 | 
			
		||||
    // Max increase is x4 (+300%)
 | 
			
		||||
    if (difficultyChange > 300) {
 | 
			
		||||
@ -74,9 +76,11 @@ export function calcDifficultyAdjustment(
 | 
			
		||||
    remainingBlocks,
 | 
			
		||||
    remainingTime,
 | 
			
		||||
    previousRetarget,
 | 
			
		||||
    previousTime: DATime,
 | 
			
		||||
    nextRetargetHeight,
 | 
			
		||||
    timeAvg,
 | 
			
		||||
    timeOffset,
 | 
			
		||||
    expectedBlocks,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,87 @@
 | 
			
		||||
<div *ngIf="showTitle" class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div>
 | 
			
		||||
<div class="card-wrapper">
 | 
			
		||||
  <div class="card">
 | 
			
		||||
    <div class="card-body more-padding">
 | 
			
		||||
      <div class="difficulty-adjustment-container" *ngIf="(isLoadingWebSocket$ | async) === false && (difficultyEpoch$ | async) as epochData; else loadingDifficulty">
 | 
			
		||||
        <div class="item">
 | 
			
		||||
          <h5 class="card-title" i18n="difficulty-box.remaining">Remaining</h5>
 | 
			
		||||
          <div class="card-text">
 | 
			
		||||
            <ng-container *ngTemplateOutlet="epochData.remainingBlocks === 1 ? blocksSingular : blocksPlural; context: {$implicit: epochData.remainingBlocks }"></ng-container>
 | 
			
		||||
            <ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
 | 
			
		||||
            <ng-template #blocksSingular let-i i18n="shared.block">{{ i }} <span class="shared-block">block</span></ng-template>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="symbol"><app-time kind="until" [time]="epochData.estimatedRetargetDate" [fastRender]="true"></app-time></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="item">
 | 
			
		||||
          <h5 class="card-title" i18n="difficulty-box.estimate">Estimate</h5>
 | 
			
		||||
          <div *ngIf="epochData.remainingBlocks < 1870; else recentlyAdjusted" class="card-text" [ngStyle]="{'color': epochData.colorAdjustments}">
 | 
			
		||||
            <span *ngIf="epochData.change > 0; else arrowDownDifficulty" >
 | 
			
		||||
              <fa-icon class="retarget-sign" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
 | 
			
		||||
            </span>
 | 
			
		||||
            <ng-template #arrowDownDifficulty >
 | 
			
		||||
              <fa-icon class="retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon>
 | 
			
		||||
            </ng-template>
 | 
			
		||||
            {{ epochData.change | absolute | number: '1.2-2' }}
 | 
			
		||||
            <span class="symbol">%</span>
 | 
			
		||||
          </div>
 | 
			
		||||
          <ng-template #recentlyAdjusted>
 | 
			
		||||
            <div class="card-text">—</div>
 | 
			
		||||
          </ng-template>
 | 
			
		||||
          <div class="symbol">
 | 
			
		||||
            <span i18n="difficulty-box.previous">Previous</span>:
 | 
			
		||||
            <span [ngStyle]="{'color': epochData.colorPreviousAdjustments}">
 | 
			
		||||
              <span *ngIf="epochData.previousRetarget > 0; else arrowDownPreviousDifficulty" >
 | 
			
		||||
                <fa-icon class="previous-retarget-sign" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
 | 
			
		||||
              </span>
 | 
			
		||||
              <ng-template #arrowDownPreviousDifficulty >
 | 
			
		||||
                <fa-icon class="previous-retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon>
 | 
			
		||||
              </ng-template>
 | 
			
		||||
              {{ epochData.previousRetarget | absolute | number: '1.2-2' }} </span> %
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="item" *ngIf="showProgress">
 | 
			
		||||
          <h5 class="card-title" i18n="difficulty-box.current-period">Current Period</h5>
 | 
			
		||||
          <div class="card-text">{{ epochData.progress | number: '1.2-2' }} <span class="symbol">%</span></div>
 | 
			
		||||
          <div class="progress small-bar">
 | 
			
		||||
            <div class="progress-bar" role="progressbar" style="width: 15%; background-color: #105fb0" [ngStyle]="{'width': epochData.base}"> </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="item" *ngIf="showHalving">
 | 
			
		||||
          <h5 class="card-title" i18n="difficulty-box.next-halving">Next Halving</h5>
 | 
			
		||||
          <div class="card-text">
 | 
			
		||||
            <ng-container *ngTemplateOutlet="epochData.blocksUntilHalving === 1 ? blocksSingular : blocksPlural; context: {$implicit: epochData.blocksUntilHalving }"></ng-container>
 | 
			
		||||
            <ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
 | 
			
		||||
            <ng-template #blocksSingular let-i i18n="shared.block">{{ i }} <span class="shared-block">block</span></ng-template>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="symbol"><app-time kind="until" [time]="epochData.timeUntilHalving" [fastRender]="true"></app-time></div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<ng-template #loadingDifficulty>
 | 
			
		||||
  <div class="difficulty-skeleton loading-container">
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="difficulty-box.remaining">Remaining</h5>
 | 
			
		||||
      <div class="card-text">
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="difficulty-box.estimate">Estimate</h5>
 | 
			
		||||
      <div class="card-text">
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="difficulty-box.current-period">Current Period</h5>
 | 
			
		||||
      <div class="card-text">
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</ng-template>
 | 
			
		||||
@ -0,0 +1,154 @@
 | 
			
		||||
.difficulty-adjustment-container {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
  justify-content: space-around;
 | 
			
		||||
  height: 76px;
 | 
			
		||||
  .shared-block {
 | 
			
		||||
    color: #ffffff66;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
  }
 | 
			
		||||
  .item {
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .card-text {
 | 
			
		||||
    .skeleton-loader {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      display: block;
 | 
			
		||||
      &:first-child {
 | 
			
		||||
        margin: 14px auto 0;
 | 
			
		||||
        max-width: 80px;
 | 
			
		||||
      }
 | 
			
		||||
      &:last-child {
 | 
			
		||||
        margin: 10px auto 0;
 | 
			
		||||
        max-width: 120px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.card {
 | 
			
		||||
  background-color: #1d1f31;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.card-title {
 | 
			
		||||
  color: #4a68b9;
 | 
			
		||||
  font-size: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.progress {
 | 
			
		||||
  display: inline-flex;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  background-color: #2d3348;
 | 
			
		||||
  height: 1.1rem;
 | 
			
		||||
  max-width: 180px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.skeleton-loader {
 | 
			
		||||
  max-width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.more-padding {
 | 
			
		||||
  padding: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.small-bar {
 | 
			
		||||
  height: 8px;
 | 
			
		||||
  top: -4px;
 | 
			
		||||
  max-width: 120px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.loading-container {
 | 
			
		||||
  min-height: 76px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.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: 24px 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.retarget-sign {
 | 
			
		||||
  margin-right: -3px;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  top: -2px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.previous-retarget-sign {
 | 
			
		||||
  margin-right: -2px;
 | 
			
		||||
  font-size: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.symbol {
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,86 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { combineLatest, Observable, timer } from 'rxjs';
 | 
			
		||||
import { map, switchMap } from 'rxjs/operators';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
 | 
			
		||||
interface EpochProgress {
 | 
			
		||||
  base: string;
 | 
			
		||||
  change: number;
 | 
			
		||||
  progress: number;
 | 
			
		||||
  remainingBlocks: number;
 | 
			
		||||
  newDifficultyHeight: number;
 | 
			
		||||
  colorAdjustments: string;
 | 
			
		||||
  colorPreviousAdjustments: string;
 | 
			
		||||
  estimatedRetargetDate: number;
 | 
			
		||||
  previousRetarget: number;
 | 
			
		||||
  blocksUntilHalving: number;
 | 
			
		||||
  timeUntilHalving: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-difficulty-mining',
 | 
			
		||||
  templateUrl: './difficulty-mining.component.html',
 | 
			
		||||
  styleUrls: ['./difficulty-mining.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class DifficultyMiningComponent implements OnInit {
 | 
			
		||||
  isLoadingWebSocket$: Observable<boolean>;
 | 
			
		||||
  difficultyEpoch$: Observable<EpochProgress>;
 | 
			
		||||
 | 
			
		||||
  @Input() showProgress = true;
 | 
			
		||||
  @Input() showHalving = false;
 | 
			
		||||
  @Input() showTitle = true;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    public stateService: StateService,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$;
 | 
			
		||||
    this.difficultyEpoch$ = combineLatest([
 | 
			
		||||
      this.stateService.blocks$.pipe(map(([block]) => block)),
 | 
			
		||||
      this.stateService.difficultyAdjustment$,
 | 
			
		||||
    ])
 | 
			
		||||
    .pipe(
 | 
			
		||||
      map(([block, da]) => {
 | 
			
		||||
        let colorAdjustments = '#ffffff66';
 | 
			
		||||
        if (da.difficultyChange > 0) {
 | 
			
		||||
          colorAdjustments = '#3bcc49';
 | 
			
		||||
        }
 | 
			
		||||
        if (da.difficultyChange < 0) {
 | 
			
		||||
          colorAdjustments = '#dc3545';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let colorPreviousAdjustments = '#dc3545';
 | 
			
		||||
        if (da.previousRetarget) {
 | 
			
		||||
          if (da.previousRetarget >= 0) {
 | 
			
		||||
            colorPreviousAdjustments = '#3bcc49';
 | 
			
		||||
          }
 | 
			
		||||
          if (da.previousRetarget === 0) {
 | 
			
		||||
            colorPreviousAdjustments = '#ffffff66';
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          colorPreviousAdjustments = '#ffffff66';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const blocksUntilHalving = 210000 - (block.height % 210000);
 | 
			
		||||
        const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000);
 | 
			
		||||
 | 
			
		||||
        const data = {
 | 
			
		||||
          base: `${da.progressPercent.toFixed(2)}%`,
 | 
			
		||||
          change: da.difficultyChange,
 | 
			
		||||
          progress: da.progressPercent,
 | 
			
		||||
          remainingBlocks: da.remainingBlocks - 1,
 | 
			
		||||
          colorAdjustments,
 | 
			
		||||
          colorPreviousAdjustments,
 | 
			
		||||
          newDifficultyHeight: da.nextRetargetHeight,
 | 
			
		||||
          estimatedRetargetDate: da.estimatedRetargetDate,
 | 
			
		||||
          previousRetarget: da.previousRetarget,
 | 
			
		||||
          blocksUntilHalving,
 | 
			
		||||
          timeUntilHalving,
 | 
			
		||||
        };
 | 
			
		||||
        return data;
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,41 @@
 | 
			
		||||
<div
 | 
			
		||||
  #tooltip
 | 
			
		||||
  *ngIf="status"
 | 
			
		||||
  class="difficulty-tooltip"
 | 
			
		||||
  [style.visibility]="status ? 'visible' : 'hidden'"
 | 
			
		||||
  [style.left]="tooltipPosition.x + 'px'"
 | 
			
		||||
  [style.top]="tooltipPosition.y + 'px'"
 | 
			
		||||
>
 | 
			
		||||
<ng-container [ngSwitch]="status">
 | 
			
		||||
  <ng-container *ngSwitchCase="'mined'">
 | 
			
		||||
    <ng-container *ngIf="isAhead">
 | 
			
		||||
      <ng-container *ngTemplateOutlet="expected === 1 ? blocksSingular : blocksPlural; context: {$implicit: expected }"></ng-container>
 | 
			
		||||
      <ng-template #blocksPlural let-i i18n="difficulty-box.expected-blocks">{{ i }} blocks expected</ng-template>
 | 
			
		||||
      <ng-template #blocksSingular let-i i18n="difficulty-box.expected-block">{{ i }} block expected</ng-template>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
    <ng-container *ngIf="!isAhead">
 | 
			
		||||
      <ng-container *ngTemplateOutlet="mined === 1 ? blocksSingular : blocksPlural; context: {$implicit: mined }"></ng-container>
 | 
			
		||||
      <ng-template #blocksPlural let-i i18n="difficulty-box.mined-blocks">{{ i }} blocks mined</ng-template>
 | 
			
		||||
      <ng-template #blocksSingular let-i i18n="difficulty-box.mined-block">{{ i }} block mined</ng-template>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
  </ng-container>
 | 
			
		||||
  <ng-container *ngSwitchCase="'remaining'">
 | 
			
		||||
    <ng-container *ngTemplateOutlet="remaining === 1 ? blocksSingular : blocksPlural; context: {$implicit: remaining }"></ng-container>
 | 
			
		||||
    <ng-template #blocksPlural let-i i18n="difficulty-box.remaining-blocks">{{ i }} blocks remaining</ng-template>
 | 
			
		||||
    <ng-template #blocksSingular let-i i18n="difficulty-box.remaining-block">{{ i }} block remaining</ng-template>
 | 
			
		||||
  </ng-container>
 | 
			
		||||
  <ng-container *ngSwitchCase="'ahead'">
 | 
			
		||||
    <ng-container *ngTemplateOutlet="ahead === 1 ? blocksSingular : blocksPlural; context: {$implicit: ahead }"></ng-container>
 | 
			
		||||
    <ng-template #blocksPlural let-i i18n="difficulty-box.blocks-ahead">{{ i }} blocks ahead</ng-template>
 | 
			
		||||
    <ng-template #blocksSingular let-i i18n="difficulty-box.block-ahead">{{ i }} block ahead</ng-template>
 | 
			
		||||
  </ng-container>
 | 
			
		||||
  <ng-container *ngSwitchCase="'behind'">
 | 
			
		||||
    <ng-container *ngTemplateOutlet="behind === 1 ? blocksSingular : blocksPlural; context: {$implicit: behind }"></ng-container>
 | 
			
		||||
    <ng-template #blocksPlural let-i i18n="difficulty-box.blocks-behind">{{ i }} blocks behind</ng-template>
 | 
			
		||||
    <ng-template #blocksSingular let-i i18n="difficulty-box.block-behind">{{ i }} block behind</ng-template>
 | 
			
		||||
  </ng-container>
 | 
			
		||||
  <ng-container *ngSwitchCase="'next'">
 | 
			
		||||
    <span i18n="@@bdf0e930eb22431140a2eaeacd809cc5f8ebd38c">next block</span>
 | 
			
		||||
  </ng-container>
 | 
			
		||||
</ng-container>
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,18 @@
 | 
			
		||||
.difficulty-tooltip {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  background: rgba(#11131f, 0.95);
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
 | 
			
		||||
  color: #b1b1b1;
 | 
			
		||||
  padding: 10px 15px;
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  pointer-events: none;
 | 
			
		||||
  max-width: 300px;
 | 
			
		||||
  min-width: 200px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
 | 
			
		||||
  p {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,66 @@
 | 
			
		||||
import { Component, ElementRef, ViewChild, Input, OnChanges } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
interface EpochProgress {
 | 
			
		||||
  base: string;
 | 
			
		||||
  change: number;
 | 
			
		||||
  progress: number;
 | 
			
		||||
  minedBlocks: number;
 | 
			
		||||
  remainingBlocks: number;
 | 
			
		||||
  expectedBlocks: number;
 | 
			
		||||
  newDifficultyHeight: number;
 | 
			
		||||
  colorAdjustments: string;
 | 
			
		||||
  colorPreviousAdjustments: string;
 | 
			
		||||
  estimatedRetargetDate: number;
 | 
			
		||||
  previousRetarget: number;
 | 
			
		||||
  blocksUntilHalving: number;
 | 
			
		||||
  timeUntilHalving: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const EPOCH_BLOCK_LENGTH = 2016; // Bitcoin mainnet
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-difficulty-tooltip',
 | 
			
		||||
  templateUrl: './difficulty-tooltip.component.html',
 | 
			
		||||
  styleUrls: ['./difficulty-tooltip.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class DifficultyTooltipComponent implements OnChanges {
 | 
			
		||||
  @Input() status: string | void;
 | 
			
		||||
  @Input() progress: EpochProgress | void = null; 
 | 
			
		||||
  @Input() cursorPosition: { x: number, y: number };
 | 
			
		||||
 | 
			
		||||
  mined: number;
 | 
			
		||||
  ahead: number;
 | 
			
		||||
  behind: number;
 | 
			
		||||
  expected: number;
 | 
			
		||||
  remaining: number;
 | 
			
		||||
  isAhead: boolean;
 | 
			
		||||
  isBehind: boolean;
 | 
			
		||||
 | 
			
		||||
  tooltipPosition = { x: 0, y: 0 };
 | 
			
		||||
 | 
			
		||||
  @ViewChild('tooltip') tooltipElement: ElementRef<HTMLCanvasElement>;
 | 
			
		||||
 | 
			
		||||
  constructor() {}
 | 
			
		||||
 | 
			
		||||
  ngOnChanges(changes): void {
 | 
			
		||||
    if (changes.cursorPosition && changes.cursorPosition.currentValue) {
 | 
			
		||||
      let x = changes.cursorPosition.currentValue.x;
 | 
			
		||||
      let y = changes.cursorPosition.currentValue.y - 50;
 | 
			
		||||
      if (this.tooltipElement) {
 | 
			
		||||
        const elementBounds = this.tooltipElement.nativeElement.getBoundingClientRect();
 | 
			
		||||
        x -= elementBounds.width / 2;
 | 
			
		||||
        x = Math.min(Math.max(x, 20), (window.innerWidth - 20 - elementBounds.width));
 | 
			
		||||
      }
 | 
			
		||||
      this.tooltipPosition = { x, y };
 | 
			
		||||
    }
 | 
			
		||||
    if ((changes.progress || changes.status) && this.progress && this.status) {
 | 
			
		||||
      this.remaining = this.progress.remainingBlocks;
 | 
			
		||||
      this.expected = this.progress.expectedBlocks;
 | 
			
		||||
      this.mined = this.progress.minedBlocks;
 | 
			
		||||
      this.ahead = Math.max(0, this.mined - this.expected);
 | 
			
		||||
      this.behind = Math.max(0, this.expected - this.mined);
 | 
			
		||||
      this.isAhead = this.ahead > 0;
 | 
			
		||||
      this.isBehind = this.behind > 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -3,17 +3,45 @@
 | 
			
		||||
  <div class="card">
 | 
			
		||||
    <div class="card-body more-padding">
 | 
			
		||||
      <div class="difficulty-adjustment-container" *ngIf="(isLoadingWebSocket$ | async) === false && (difficultyEpoch$ | async) as epochData; else loadingDifficulty">
 | 
			
		||||
        <div class="epoch-progress">
 | 
			
		||||
          <svg class="epoch-blocks" height="22px" width="100%" viewBox="0 0 234 9" shape-rendering="crispEdges" preserveAspectRatio="none">
 | 
			
		||||
            <defs>
 | 
			
		||||
              <linearGradient id="diff-gradient" x1="0%" y1="0%" x2="100%" y2="0%" gradientUnits="userSpaceOnUse">
 | 
			
		||||
                <stop offset="0%" stop-color="#105fb0" />
 | 
			
		||||
                <stop offset="100%" stop-color="#9339f4" />
 | 
			
		||||
              </linearGradient>
 | 
			
		||||
              <linearGradient id="diff-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%" gradientUnits="userSpaceOnUse">
 | 
			
		||||
                <stop offset="0%" stop-color="#2486eb" />
 | 
			
		||||
                <stop offset="100%" stop-color="#ae6af7" />
 | 
			
		||||
              </linearGradient>
 | 
			
		||||
            </defs>
 | 
			
		||||
            <rect
 | 
			
		||||
              *ngFor="let rect of shapes"
 | 
			
		||||
              [attr.x]="rect.x" [attr.y]="rect.y"
 | 
			
		||||
              [attr.width]="rect.w" [attr.height]="rect.h"
 | 
			
		||||
              class="rect {{rect.status}}"
 | 
			
		||||
              [class.hover]="hoverSection && rect.status === hoverSection.status"
 | 
			
		||||
              (pointerover)="onHover($event, rect);"
 | 
			
		||||
              (pointerout)="onBlur($event);"
 | 
			
		||||
            >
 | 
			
		||||
              <animate
 | 
			
		||||
                *ngIf="rect.status === 'next'"
 | 
			
		||||
                attributeType="XML"
 | 
			
		||||
                attributeName="fill"
 | 
			
		||||
                [attr.values]="'#fff;' + (rect.expected ? '#D81B60' : '#2d3348') + ';#fff'"
 | 
			
		||||
                dur="2s"
 | 
			
		||||
                repeatCount="indefinite"/>
 | 
			
		||||
            </rect>
 | 
			
		||||
          </svg>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="difficulty-stats">
 | 
			
		||||
          <div class="item">
 | 
			
		||||
          <h5 class="card-title" i18n="difficulty-box.remaining">Remaining</h5>
 | 
			
		||||
            <div class="card-text">
 | 
			
		||||
            <ng-container *ngTemplateOutlet="epochData.remainingBlocks === 1 ? blocksSingular : blocksPlural; context: {$implicit: epochData.remainingBlocks }"></ng-container>
 | 
			
		||||
            <ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
 | 
			
		||||
            <ng-template #blocksSingular let-i i18n="shared.block">{{ i }} <span class="shared-block">block</span></ng-template>
 | 
			
		||||
              ~<app-time [time]="epochData.timeAvg / 1000" [forceFloorOnTimeIntervals]="['minute']" [fractionDigits]="1"></app-time>
 | 
			
		||||
            </div>
 | 
			
		||||
          <div class="symbol"><app-time kind="until" [time]="epochData.estimatedRetargetDate" [fastRender]="true"></app-time></div>
 | 
			
		||||
            <div class="symbol" i18n="difficulty-box.average-block-interval">Average interval</div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="item">
 | 
			
		||||
          <h5 class="card-title" i18n="difficulty-box.estimate">Estimate</h5>
 | 
			
		||||
            <div *ngIf="epochData.remainingBlocks < 1870; else recentlyAdjusted" class="card-text" [ngStyle]="{'color': epochData.colorAdjustments}">
 | 
			
		||||
              <span *ngIf="epochData.change > 0; else arrowDownDifficulty" >
 | 
			
		||||
                <fa-icon class="retarget-sign" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
 | 
			
		||||
@ -39,21 +67,12 @@
 | 
			
		||||
                {{ epochData.previousRetarget | absolute | number: '1.2-2' }} </span> %
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        <div class="item" *ngIf="showProgress">
 | 
			
		||||
          <h5 class="card-title" i18n="difficulty-box.current-period">Current Period</h5>
 | 
			
		||||
          <div class="card-text">{{ epochData.progress | number: '1.2-2' }} <span class="symbol">%</span></div>
 | 
			
		||||
          <div class="progress small-bar">
 | 
			
		||||
            <div class="progress-bar" role="progressbar" style="width: 15%; background-color: #105fb0" [ngStyle]="{'width': epochData.base}"> </div>
 | 
			
		||||
          <div class="item">
 | 
			
		||||
            <div class="card-text"><app-time kind="until" [time]="epochData.estimatedRetargetDate" [fastRender]="true"></app-time></div>
 | 
			
		||||
            <div class="symbol">
 | 
			
		||||
              {{ epochData.retargetDateString }}
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        <div class="item" *ngIf="showHalving">
 | 
			
		||||
          <h5 class="card-title" i18n="difficulty-box.next-halving">Next Halving</h5>
 | 
			
		||||
          <div class="card-text">
 | 
			
		||||
            <ng-container *ngTemplateOutlet="epochData.blocksUntilHalving === 1 ? blocksSingular : blocksPlural; context: {$implicit: epochData.blocksUntilHalving }"></ng-container>
 | 
			
		||||
            <ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
 | 
			
		||||
            <ng-template #blocksSingular let-i i18n="shared.block">{{ i }} <span class="shared-block">block</span></ng-template>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="symbol"><app-time kind="until" [time]="epochData.timeUntilHalving" [fastRender]="true"></app-time></div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@ -61,23 +80,23 @@
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<ng-template #loadingDifficulty>
 | 
			
		||||
  <div class="epoch-progress">
 | 
			
		||||
    <div class="skeleton-loader"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="difficulty-skeleton loading-container">
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="difficulty-box.remaining">Remaining</h5>
 | 
			
		||||
      <div class="card-text">
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="difficulty-box.estimate">Estimate</h5>
 | 
			
		||||
      <div class="card-text">
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="difficulty-box.current-period">Current Period</h5>
 | 
			
		||||
      <div class="card-text">
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
@ -85,3 +104,10 @@
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<app-difficulty-tooltip
 | 
			
		||||
  *ngIf="hoverSection && (isLoadingWebSocket$ | async) === false && (difficultyEpoch$ | async) as epochData"
 | 
			
		||||
  [cursorPosition]="tooltipPosition"
 | 
			
		||||
  [status]="hoverSection.status"
 | 
			
		||||
  [progress]="epochData"
 | 
			
		||||
></app-difficulty-tooltip>
 | 
			
		||||
@ -1,8 +1,14 @@
 | 
			
		||||
.difficulty-adjustment-container {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.difficulty-stats {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
  justify-content: space-around;
 | 
			
		||||
  height: 76px;
 | 
			
		||||
  height: 50.5px;
 | 
			
		||||
  .shared-block {
 | 
			
		||||
    color: #ffffff66;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
@ -24,8 +30,8 @@
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .card-text {
 | 
			
		||||
    font-size: 22px;
 | 
			
		||||
    margin-top: -9px;
 | 
			
		||||
    font-size: 20px;
 | 
			
		||||
    margin: auto;
 | 
			
		||||
    position: relative;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -33,7 +39,9 @@
 | 
			
		||||
 | 
			
		||||
.difficulty-skeleton {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
  justify-content: space-around;
 | 
			
		||||
  height: 50.5px;
 | 
			
		||||
  @media (min-width: 376px) {
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
  }
 | 
			
		||||
@ -65,7 +73,7 @@
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      display: block;
 | 
			
		||||
      &:first-child {
 | 
			
		||||
        margin: 14px auto 0;
 | 
			
		||||
        margin: 10px auto 4px;
 | 
			
		||||
        max-width: 80px;
 | 
			
		||||
      }
 | 
			
		||||
      &:last-child {
 | 
			
		||||
@ -109,7 +117,7 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.loading-container {
 | 
			
		||||
  min-height: 76px;
 | 
			
		||||
  min-height: 50.5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.main-title {
 | 
			
		||||
@ -133,7 +141,7 @@
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    justify-content: space-around;
 | 
			
		||||
    padding: 24px 20px;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -152,3 +160,49 @@
 | 
			
		||||
.symbol {
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.epoch-progress {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 22px;
 | 
			
		||||
  margin-bottom: 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.epoch-blocks {
 | 
			
		||||
  display: block;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  background: #2d3348;
 | 
			
		||||
 | 
			
		||||
  .rect {
 | 
			
		||||
    fill: #2d3348;
 | 
			
		||||
 | 
			
		||||
    &.behind {
 | 
			
		||||
      fill: #D81B60;
 | 
			
		||||
    }
 | 
			
		||||
    &.mined {
 | 
			
		||||
      fill: url(#diff-gradient);
 | 
			
		||||
    }
 | 
			
		||||
    &.ahead {
 | 
			
		||||
      fill: #1a9436;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.hover {
 | 
			
		||||
      fill: #535e84;
 | 
			
		||||
      &.behind {
 | 
			
		||||
        fill: #e94d86;
 | 
			
		||||
      }
 | 
			
		||||
      &.mined {
 | 
			
		||||
        fill: url(#diff-hover-gradient);
 | 
			
		||||
      }
 | 
			
		||||
      &.ahead {
 | 
			
		||||
        fill: #29d951;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.blocks-ahead {
 | 
			
		||||
  color: #3bcc49;
 | 
			
		||||
}
 | 
			
		||||
.blocks-behind {
 | 
			
		||||
  color: #D81B60;
 | 
			
		||||
}
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { ChangeDetectionStrategy, Component, HostListener, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
 | 
			
		||||
import { combineLatest, Observable, timer } from 'rxjs';
 | 
			
		||||
import { map, switchMap } from 'rxjs/operators';
 | 
			
		||||
import { StateService } from '../..//services/state.service';
 | 
			
		||||
@ -7,16 +7,33 @@ interface EpochProgress {
 | 
			
		||||
  base: string;
 | 
			
		||||
  change: number;
 | 
			
		||||
  progress: number;
 | 
			
		||||
  minedBlocks: number;
 | 
			
		||||
  remainingBlocks: number;
 | 
			
		||||
  expectedBlocks: number;
 | 
			
		||||
  newDifficultyHeight: number;
 | 
			
		||||
  colorAdjustments: string;
 | 
			
		||||
  colorPreviousAdjustments: string;
 | 
			
		||||
  estimatedRetargetDate: number;
 | 
			
		||||
  retargetDateString: string;
 | 
			
		||||
  previousRetarget: number;
 | 
			
		||||
  blocksUntilHalving: number;
 | 
			
		||||
  timeUntilHalving: number;
 | 
			
		||||
  timeAvg: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type BlockStatus = 'mined' | 'behind' | 'ahead' | 'next' | 'remaining';
 | 
			
		||||
 | 
			
		||||
interface DiffShape {
 | 
			
		||||
  x: number;
 | 
			
		||||
  y: number;
 | 
			
		||||
  w: number;
 | 
			
		||||
  h: number;
 | 
			
		||||
  status: BlockStatus;
 | 
			
		||||
  expected: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const EPOCH_BLOCK_LENGTH = 2016; // Bitcoin mainnet
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-difficulty',
 | 
			
		||||
  templateUrl: './difficulty.component.html',
 | 
			
		||||
@ -24,15 +41,27 @@ interface EpochProgress {
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class DifficultyComponent implements OnInit {
 | 
			
		||||
  isLoadingWebSocket$: Observable<boolean>;
 | 
			
		||||
  difficultyEpoch$: Observable<EpochProgress>;
 | 
			
		||||
 | 
			
		||||
  @Input() showProgress = true;
 | 
			
		||||
  @Input() showHalving = false;
 | 
			
		||||
  @Input() showTitle = true;
 | 
			
		||||
 
 | 
			
		||||
  isLoadingWebSocket$: Observable<boolean>;
 | 
			
		||||
  difficultyEpoch$: Observable<EpochProgress>;
 | 
			
		||||
 | 
			
		||||
  epochStart: number;
 | 
			
		||||
  currentHeight: number;
 | 
			
		||||
  currentIndex: number;
 | 
			
		||||
  expectedHeight: number;
 | 
			
		||||
  expectedIndex: number;
 | 
			
		||||
  difference: number;
 | 
			
		||||
  shapes: DiffShape[];
 | 
			
		||||
 | 
			
		||||
  tooltipPosition = { x: 0, y: 0 };
 | 
			
		||||
  hoverSection: DiffShape | void;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    public stateService: StateService,
 | 
			
		||||
    @Inject(LOCALE_ID) private locale: string,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
@ -65,22 +94,110 @@ export class DifficultyComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
        const blocksUntilHalving = 210000 - (block.height % 210000);
 | 
			
		||||
        const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000);
 | 
			
		||||
        const newEpochStart = Math.floor(this.stateService.latestBlockHeight / EPOCH_BLOCK_LENGTH) * EPOCH_BLOCK_LENGTH;
 | 
			
		||||
        const newExpectedHeight = Math.floor(newEpochStart + da.expectedBlocks);
 | 
			
		||||
 | 
			
		||||
        if (newEpochStart !== this.epochStart || newExpectedHeight !== this.expectedHeight || this.currentHeight !== this.stateService.latestBlockHeight) {
 | 
			
		||||
          this.epochStart = newEpochStart;
 | 
			
		||||
          this.expectedHeight = newExpectedHeight;
 | 
			
		||||
          this.currentHeight = this.stateService.latestBlockHeight;
 | 
			
		||||
          this.currentIndex = this.currentHeight - this.epochStart;
 | 
			
		||||
          this.expectedIndex = Math.min(this.expectedHeight - this.epochStart, 2106) - 1;
 | 
			
		||||
          this.difference = this.currentIndex - this.expectedIndex;
 | 
			
		||||
 | 
			
		||||
          this.shapes = [];
 | 
			
		||||
          this.shapes = this.shapes.concat(this.blocksToShapes(
 | 
			
		||||
            0, Math.min(this.currentIndex, this.expectedIndex), 'mined', true
 | 
			
		||||
          ));
 | 
			
		||||
          this.shapes = this.shapes.concat(this.blocksToShapes(
 | 
			
		||||
            this.currentIndex + 1, this.expectedIndex, 'behind', true
 | 
			
		||||
          ));
 | 
			
		||||
          this.shapes = this.shapes.concat(this.blocksToShapes(
 | 
			
		||||
            this.expectedIndex + 1, this.currentIndex, 'ahead', false
 | 
			
		||||
          ));
 | 
			
		||||
          if (this.currentIndex < 2105) {
 | 
			
		||||
            this.shapes = this.shapes.concat(this.blocksToShapes(
 | 
			
		||||
              this.currentIndex + 1, this.currentIndex + 1, 'next', (this.expectedIndex > this.currentIndex)
 | 
			
		||||
            ));
 | 
			
		||||
          }
 | 
			
		||||
          this.shapes = this.shapes.concat(this.blocksToShapes(
 | 
			
		||||
            Math.max(this.currentIndex + 2, this.expectedIndex + 1), 2105, 'remaining', false
 | 
			
		||||
          ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        let retargetDateString;
 | 
			
		||||
        if (da.remainingBlocks > 1870) {
 | 
			
		||||
          retargetDateString = (new Date(da.estimatedRetargetDate)).toLocaleDateString(this.locale, { month: 'long', day: 'numeric' });
 | 
			
		||||
        } else {
 | 
			
		||||
          retargetDateString = (new Date(da.estimatedRetargetDate)).toLocaleTimeString(this.locale, { month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const data = {
 | 
			
		||||
          base: `${da.progressPercent.toFixed(2)}%`,
 | 
			
		||||
          change: da.difficultyChange,
 | 
			
		||||
          progress: da.progressPercent,
 | 
			
		||||
          remainingBlocks: da.remainingBlocks,
 | 
			
		||||
          minedBlocks: this.currentIndex + 1,
 | 
			
		||||
          remainingBlocks: da.remainingBlocks - 1,
 | 
			
		||||
          expectedBlocks: Math.floor(da.expectedBlocks),
 | 
			
		||||
          colorAdjustments,
 | 
			
		||||
          colorPreviousAdjustments,
 | 
			
		||||
          newDifficultyHeight: da.nextRetargetHeight,
 | 
			
		||||
          estimatedRetargetDate: da.estimatedRetargetDate,
 | 
			
		||||
          retargetDateString,
 | 
			
		||||
          previousRetarget: da.previousRetarget,
 | 
			
		||||
          blocksUntilHalving,
 | 
			
		||||
          timeUntilHalving,
 | 
			
		||||
          timeAvg: da.timeAvg,
 | 
			
		||||
        };
 | 
			
		||||
        return data;
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  blocksToShapes(start: number, end: number, status: BlockStatus, expected: boolean = false): DiffShape[] {
 | 
			
		||||
    const startY = start % 9;
 | 
			
		||||
    const startX = Math.floor(start / 9);
 | 
			
		||||
    const endY = (end % 9);
 | 
			
		||||
    const endX = Math.floor(end / 9);
 | 
			
		||||
 | 
			
		||||
    if (startX > endX) {
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (startX === endX) {
 | 
			
		||||
      return [{
 | 
			
		||||
        x: startX, y: startY, w: 1, h: 1 + endY - startY, status, expected
 | 
			
		||||
      }];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const shapes = [];
 | 
			
		||||
    shapes.push({
 | 
			
		||||
      x: startX, y: startY, w: 1, h: 9 - startY, status, expected
 | 
			
		||||
    });
 | 
			
		||||
    shapes.push({
 | 
			
		||||
      x: endX, y: 0, w: 1, h: endY + 1, status, expected
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (startX < endX - 1) {
 | 
			
		||||
      shapes.push({
 | 
			
		||||
        x: startX + 1, y: 0, w: endX - startX - 1, h: 9, status, expected
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return shapes;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @HostListener('pointermove', ['$event'])
 | 
			
		||||
  onPointerMove(event) {
 | 
			
		||||
    this.tooltipPosition = { x: event.clientX, y: event.clientY };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onHover(event, rect): void {
 | 
			
		||||
    this.hoverSection = rect;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onBlur(event): void {
 | 
			
		||||
    this.hoverSection = null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,7 @@
 | 
			
		||||
    <!-- difficulty adjustment -->
 | 
			
		||||
    <div class="col">
 | 
			
		||||
      <div class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div>
 | 
			
		||||
      <app-difficulty [attr.data-cy]="'difficulty-adjustment'" [showTitle]="false" [showProgress]="false" [showHalving]="true"></app-difficulty>
 | 
			
		||||
      <app-difficulty-mining [attr.data-cy]="'difficulty-adjustment'" [showTitle]="false" [showProgress]="false" [showHalving]="true"></app-difficulty-mining>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- pool distribution -->
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
  @Input() fixedRender = false;
 | 
			
		||||
  @Input() relative = false;
 | 
			
		||||
  @Input() forceFloorOnTimeIntervals: string[];
 | 
			
		||||
  @Input() fractionDigits: number = 0;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private ref: ChangeDetectorRef,
 | 
			
		||||
@ -88,7 +89,12 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
      } else {
 | 
			
		||||
        counter = Math.round(seconds / this.intervals[i]);
 | 
			
		||||
      }
 | 
			
		||||
      const dateStrings = dates(counter);
 | 
			
		||||
      let rounded = counter;
 | 
			
		||||
      if (this.fractionDigits) {
 | 
			
		||||
        const roundFactor = Math.pow(10,this.fractionDigits);
 | 
			
		||||
        rounded = Math.round((seconds / this.intervals[i]) * roundFactor) / roundFactor;
 | 
			
		||||
      }
 | 
			
		||||
      const dateStrings = dates(rounded);
 | 
			
		||||
      if (counter > 0) {
 | 
			
		||||
        switch (this.kind) {
 | 
			
		||||
          case 'since':
 | 
			
		||||
 | 
			
		||||
@ -33,9 +33,11 @@ export interface DifficultyAdjustment {
 | 
			
		||||
  remainingBlocks: number;
 | 
			
		||||
  remainingTime: number;
 | 
			
		||||
  previousRetarget: number;
 | 
			
		||||
  previousTime: number;
 | 
			
		||||
  nextRetargetHeight: number;
 | 
			
		||||
  timeAvg: number;
 | 
			
		||||
  timeOffset: number;
 | 
			
		||||
  expectedBlocks: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface AddressInformation {
 | 
			
		||||
 | 
			
		||||
@ -58,6 +58,8 @@ import { AssetsNavComponent } from '../components/assets/assets-nav/assets-nav.c
 | 
			
		||||
import { StatusViewComponent } from '../components/status-view/status-view.component';
 | 
			
		||||
import { FeesBoxComponent } from '../components/fees-box/fees-box.component';
 | 
			
		||||
import { DifficultyComponent } from '../components/difficulty/difficulty.component';
 | 
			
		||||
import { DifficultyTooltipComponent } from '../components/difficulty/difficulty-tooltip.component';
 | 
			
		||||
import { DifficultyMiningComponent } from '../components/difficulty-mining/difficulty-mining.component';
 | 
			
		||||
import { TermsOfServiceComponent } from '../components/terms-of-service/terms-of-service.component';
 | 
			
		||||
import { TxBowtieGraphComponent } from '../components/tx-bowtie-graph/tx-bowtie-graph.component';
 | 
			
		||||
import { TxBowtieGraphTooltipComponent } from '../components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component';
 | 
			
		||||
@ -133,6 +135,8 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
 | 
			
		||||
    StatusViewComponent,
 | 
			
		||||
    FeesBoxComponent,
 | 
			
		||||
    DifficultyComponent,
 | 
			
		||||
    DifficultyMiningComponent,
 | 
			
		||||
    DifficultyTooltipComponent,
 | 
			
		||||
    TxBowtieGraphComponent,
 | 
			
		||||
    TxBowtieGraphTooltipComponent,
 | 
			
		||||
    TermsOfServiceComponent,
 | 
			
		||||
@ -234,6 +238,8 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
 | 
			
		||||
    StatusViewComponent,
 | 
			
		||||
    FeesBoxComponent,
 | 
			
		||||
    DifficultyComponent,
 | 
			
		||||
    DifficultyMiningComponent,
 | 
			
		||||
    DifficultyTooltipComponent,
 | 
			
		||||
    TxBowtieGraphComponent,
 | 
			
		||||
    TxBowtieGraphTooltipComponent,
 | 
			
		||||
    TermsOfServiceComponent,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user