diff --git a/frontend/src/app/components/address-graph/address-graph.component.html b/frontend/src/app/components/address-graph/address-graph.component.html index df4cdf330..32e16913a 100644 --- a/frontend/src/app/components/address-graph/address-graph.component.html +++ b/frontend/src/app/components/address-graph/address-graph.component.html @@ -1,12 +1,6 @@
-
-
- Balance History -
-
-
diff --git a/frontend/src/app/components/address-graph/address-graph.component.scss b/frontend/src/app/components/address-graph/address-graph.component.scss index a118549fb..3752203c1 100644 --- a/frontend/src/app/components/address-graph/address-graph.component.scss +++ b/frontend/src/app/components/address-graph/address-graph.component.scss @@ -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%; diff --git a/frontend/src/app/components/address-graph/address-graph.component.ts b/frontend/src/app/components/address-graph/address-graph.component.ts index 26a1bd408..a5db9602d 100644 --- a/frontend/src/app/components/address-graph/address-graph.component.ts +++ b/frontend/src/app/components/address-graph/address-graph.component.ts @@ -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 = 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); } diff --git a/frontend/src/app/components/address/address.component.html b/frontend/src/app/components/address/address.component.html index 531b97464..661e84869 100644 --- a/frontend/src/app/components/address/address.component.html +++ b/frontend/src/app/components/address/address.component.html @@ -53,10 +53,20 @@
+
+

Balance History

+
+
+ all + | + recent +
- +
diff --git a/frontend/src/app/components/address/address.component.scss b/frontend/src/app/components/address/address.component.scss index 7107c73f2..78ca0e80d 100644 --- a/frontend/src/app/components/address/address.component.scss +++ b/frontend/src/app/components/address/address.component.scss @@ -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); +} \ No newline at end of file diff --git a/frontend/src/app/components/address/address.component.ts b/frontend/src/app/components/address/address.component.ts index 95abe4ac1..19712a702 100644 --- a/frontend/src/app/components/address/address.component.ts +++ b/frontend/src/app/components/address/address.component.ts @@ -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();