Polish address balance graph, add period toggle
This commit is contained in:
		
							parent
							
								
									20df70c449
								
							
						
					
					
						commit
						a582a3b0ed
					
				@ -1,12 +1,6 @@
 | 
			
		||||
<app-indexing-progress *ngIf="!widget"></app-indexing-progress>
 | 
			
		||||
 | 
			
		||||
<div [class.full-container]="!widget">
 | 
			
		||||
  <div *ngIf="!widget" class="card-header mb-0 mb-md-2">
 | 
			
		||||
    <div class="d-flex d-md-block align-items-baseline">
 | 
			
		||||
      <span i18n="address.balance-history">Balance History</span>
 | 
			
		||||
    </div>  
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <ng-container *ngIf="!error">
 | 
			
		||||
    <div [class]="!widget ? 'chart' : 'chart-widget'" *browserOnly [style]="{ height: widget ? ((height + 20) + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
 | 
			
		||||
      (chartInit)="onChartInit($event)">
 | 
			
		||||
 | 
			
		||||
@ -45,23 +45,8 @@
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex: 1;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  padding-bottom: 20px;
 | 
			
		||||
  padding-bottom: 10px;
 | 
			
		||||
  padding-right: 10px;
 | 
			
		||||
  @media (max-width: 992px) {
 | 
			
		||||
    padding-bottom: 25px;
 | 
			
		||||
  }
 | 
			
		||||
  @media (max-width: 829px) {
 | 
			
		||||
    padding-bottom: 50px;
 | 
			
		||||
  }
 | 
			
		||||
  @media (max-width: 767px) {
 | 
			
		||||
    padding-bottom: 25px;
 | 
			
		||||
  }
 | 
			
		||||
  @media (max-width: 629px) {
 | 
			
		||||
    padding-bottom: 55px;
 | 
			
		||||
  }
 | 
			
		||||
  @media (max-width: 567px) {
 | 
			
		||||
    padding-bottom: 55px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.chart-widget {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnChanges, SimpleChanges } from '@angular/core';
 | 
			
		||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
 | 
			
		||||
import { echarts, EChartsOption } from '../../graphs/echarts';
 | 
			
		||||
import { Observable, of } from 'rxjs';
 | 
			
		||||
import { BehaviorSubject, Observable, Subscription, combineLatest, of } from 'rxjs';
 | 
			
		||||
import { catchError } from 'rxjs/operators';
 | 
			
		||||
import { AddressTxSummary, ChainStats } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
@ -32,7 +32,7 @@ const periodSeconds = {
 | 
			
		||||
  `],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class AddressGraphComponent implements OnChanges {
 | 
			
		||||
export class AddressGraphComponent implements OnChanges, OnDestroy {
 | 
			
		||||
  @Input() address: string;
 | 
			
		||||
  @Input() isPubkey: boolean = false;
 | 
			
		||||
  @Input() stats: ChainStats;
 | 
			
		||||
@ -46,6 +46,9 @@ export class AddressGraphComponent implements OnChanges {
 | 
			
		||||
  data: any[] = [];
 | 
			
		||||
  hoverData: any[] = [];
 | 
			
		||||
 | 
			
		||||
  subscription: Subscription;
 | 
			
		||||
  redraw$: BehaviorSubject<boolean> = new BehaviorSubject(false);
 | 
			
		||||
 | 
			
		||||
  chartOptions: EChartsOption = {};
 | 
			
		||||
  chartInitOptions = {
 | 
			
		||||
    renderer: 'svg',
 | 
			
		||||
@ -70,24 +73,38 @@ export class AddressGraphComponent implements OnChanges {
 | 
			
		||||
    if (!this.address || !this.stats) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    (this.addressSummary$ || (this.isPubkey
 | 
			
		||||
      ? this.electrsApiService.getScriptHashSummary$((this.address.length === 66 ? '21' : '41') + this.address + 'ac')
 | 
			
		||||
      : this.electrsApiService.getAddressSummary$(this.address)).pipe(
 | 
			
		||||
      catchError(e => {
 | 
			
		||||
        this.error = `Failed to fetch address balance history: ${e?.status || ''} ${e?.statusText || 'unknown error'}`;
 | 
			
		||||
        return of(null);
 | 
			
		||||
      }),
 | 
			
		||||
    )).subscribe(addressSummary => {
 | 
			
		||||
      if (addressSummary) {
 | 
			
		||||
        this.error = null;
 | 
			
		||||
        this.prepareChartOptions(addressSummary);
 | 
			
		||||
    if (changes.address || changes.isPubkey || changes.addressSummary$) {
 | 
			
		||||
      if (this.subscription) {
 | 
			
		||||
        this.subscription.unsubscribe();
 | 
			
		||||
      }
 | 
			
		||||
      this.isLoading = false;
 | 
			
		||||
      this.cd.markForCheck();
 | 
			
		||||
    });
 | 
			
		||||
      this.subscription = combineLatest([
 | 
			
		||||
        this.redraw$,
 | 
			
		||||
        (this.addressSummary$ || (this.isPubkey
 | 
			
		||||
          ? this.electrsApiService.getScriptHashSummary$((this.address.length === 66 ? '21' : '41') + this.address + 'ac')
 | 
			
		||||
          : this.electrsApiService.getAddressSummary$(this.address)).pipe(
 | 
			
		||||
          catchError(e => {
 | 
			
		||||
            this.error = `Failed to fetch address balance history: ${e?.status || ''} ${e?.statusText || 'unknown error'}`;
 | 
			
		||||
            return of(null);
 | 
			
		||||
          }),
 | 
			
		||||
        ))
 | 
			
		||||
      ]).subscribe(([redraw, addressSummary]) => {
 | 
			
		||||
        if (addressSummary) {
 | 
			
		||||
          this.error = null;
 | 
			
		||||
          this.prepareChartOptions(addressSummary);
 | 
			
		||||
        }
 | 
			
		||||
        this.isLoading = false;
 | 
			
		||||
        this.cd.markForCheck();
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      // re-trigger subscription
 | 
			
		||||
      this.redraw$.next(true);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  prepareChartOptions(summary): void {
 | 
			
		||||
    if (!summary || !this.stats) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    let total = (this.stats.funded_txo_sum - this.stats.spent_txo_sum);
 | 
			
		||||
    this.data = summary.map(d => {
 | 
			
		||||
      const balance = total;
 | 
			
		||||
@ -104,8 +121,8 @@ export class AddressGraphComponent implements OnChanges {
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const maxValue = this.data.reduce((acc, d) => Math.max(acc, Math.abs(d[1] || d.value[1])), 0);
 | 
			
		||||
    const minValue = this.data.reduce((acc, d) => Math.min(acc, Math.abs(d[1] || d.value[1])), maxValue);
 | 
			
		||||
    const maxValue = this.data.reduce((acc, d) => Math.max(acc, Math.abs(d[1] ?? d.value[1])), 0);
 | 
			
		||||
    const minValue = this.data.reduce((acc, d) => Math.min(acc, Math.abs(d[1] ?? d.value[1])), maxValue);
 | 
			
		||||
 | 
			
		||||
    this.chartOptions = {
 | 
			
		||||
      color: [
 | 
			
		||||
@ -230,6 +247,10 @@ export class AddressGraphComponent implements OnChanges {
 | 
			
		||||
    this.chartInstance.on('click', 'series', this.onChartClick.bind(this));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy(): void {
 | 
			
		||||
    this.subscription.unsubscribe();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isMobile() {
 | 
			
		||||
    return (window.innerWidth <= 767.98);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -53,10 +53,20 @@
 | 
			
		||||
 | 
			
		||||
    <ng-container *ngIf="(stateService.backend$ | async) === 'esplora' && address && transactions && transactions.length > 2">
 | 
			
		||||
      <br>
 | 
			
		||||
      <div class="title-tx">
 | 
			
		||||
        <h2 class="text-left" i18n="address.balance-history">Balance History</h2>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="box">
 | 
			
		||||
        <div class="widget-toggler" *ngIf="showBalancePeriod()">
 | 
			
		||||
          <a href="" (click)="setBalancePeriod('all')" class="toggler-option"
 | 
			
		||||
            [ngClass]="{'inactive': balancePeriod === 'all'}"><small i18n="all">all</small></a>
 | 
			
		||||
          <span style="color: var(--transparent-fg); font-size: 8px"> | </span>
 | 
			
		||||
          <a href="" (click)="setBalancePeriod('1m')" class="toggler-option"
 | 
			
		||||
            [ngClass]="{'inactive': balancePeriod === '1m'}"><small i18n="recent">recent</small></a>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="row">
 | 
			
		||||
          <div class="col-md">
 | 
			
		||||
            <app-address-graph [address]="addressString" [isPubkey]="address?.is_pubkey" [stats]="address.chain_stats" />
 | 
			
		||||
            <app-address-graph [address]="addressString" [isPubkey]="address?.is_pubkey" [stats]="address.chain_stats" [period]="balancePeriod" />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@ -109,3 +109,19 @@ h1 {
 | 
			
		||||
    flex-grow: 0.5;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.widget-toggler {
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: -20px;
 | 
			
		||||
  right: 3px;
 | 
			
		||||
  text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.toggler-option {
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.inactive {
 | 
			
		||||
  color: var(--transparent-fg);
 | 
			
		||||
}
 | 
			
		||||
@ -38,6 +38,8 @@ export class AddressComponent implements OnInit, OnDestroy {
 | 
			
		||||
  txCount = 0;
 | 
			
		||||
  received = 0;
 | 
			
		||||
  sent = 0;
 | 
			
		||||
  now = Date.now() / 1000;
 | 
			
		||||
  balancePeriod: 'all' | '1m' = 'all';
 | 
			
		||||
 | 
			
		||||
  private tempTransactions: Transaction[];
 | 
			
		||||
  private timeTxIndexes: number[];
 | 
			
		||||
@ -174,6 +176,10 @@ export class AddressComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
        this.transactions = this.tempTransactions;
 | 
			
		||||
        this.isLoadingTransactions = false;
 | 
			
		||||
 | 
			
		||||
        if (!this.showBalancePeriod()) {
 | 
			
		||||
          this.setBalancePeriod('all');
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      (error) => {
 | 
			
		||||
        console.log(error);
 | 
			
		||||
@ -296,6 +302,18 @@ export class AddressComponent implements OnInit, OnDestroy {
 | 
			
		||||
    this.txCount = this.address.chain_stats.tx_count + this.address.mempool_stats.tx_count;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setBalancePeriod(period: 'all' | '1m'): boolean {
 | 
			
		||||
    this.balancePeriod = period;
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  showBalancePeriod(): boolean {
 | 
			
		||||
    return this.transactions?.length && (
 | 
			
		||||
      !this.transactions[0].status?.confirmed
 | 
			
		||||
      || this.transactions[0].status.block_time > (this.now - (60 * 60 * 24 * 30))
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
    this.mainSubscription.unsubscribe();
 | 
			
		||||
    this.mempoolTxSubscription.unsubscribe();
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user