Merge branch 'master' into mononaut/mobile-chain-jumping

This commit is contained in:
Felipe Knorr Kuhn
2023-02-19 14:00:10 -08:00
committed by GitHub
104 changed files with 3171 additions and 1737 deletions

View File

@@ -68,8 +68,8 @@
.community-sponsor {
img {
width: 67px;
height: 67px;
width: 57px;
height: 57px;
}
}

View File

@@ -1,4 +1,4 @@
<div class="container-xl">
<div class="container-xl" [class.liquid-address]="network === 'liquid' || network === 'liquidtestnet'">
<div class="title-address">
<h1 i18n="shared.address">Address</h1>
<div class="tx-link">
@@ -15,7 +15,7 @@
<div class="row">
<div class="col-md">
<table class="table table-borderless table-striped">
<table class="table table-borderless table-striped address-table">
<tbody>
<tr *ngIf="addressInfo && addressInfo.unconfidential">
<td i18n="address.unconfidential">Unconfidential</td>

View File

@@ -91,3 +91,20 @@ h1 {
margin-bottom: 10px;
}
}
.liquid-address {
.address-table {
table-layout: fixed;
tr td:first-child {
width: 170px;
}
tr td:last-child {
width: 80%;
}
}
.qrcode-col {
flex-grow: 0.5;
}
}

View File

@@ -1,5 +1,5 @@
<ng-container *ngIf="!noFiat && (viewFiat$ | async) && (conversions$ | async) as conversions; else viewFiatVin">
<span class="fiat">{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ conversions.USD * (satoshis / 100000000) | currency:'USD':'symbol':'1.2-2' }}</span>
<span class="fiat">{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ (conversions ? conversions[currency] : 0) * satoshis / 100000000 | fiatCurrency : digitsInfo : currency }}</span>
</ng-container>
<ng-template #viewFiatVin>
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, Input, ChangeDetectionStrategy } from '@angular/core';
import { Component, OnInit, OnDestroy, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { StateService } from '../../services/state.service';
import { Observable, Subscription } from 'rxjs';
@@ -10,10 +10,12 @@ import { Observable, Subscription } from 'rxjs';
})
export class AmountComponent implements OnInit, OnDestroy {
conversions$: Observable<any>;
currency: string;
viewFiat$: Observable<boolean>;
network = '';
stateSubscription: Subscription;
currencySubscription: Subscription;
@Input() satoshis: number;
@Input() digitsInfo = '1.8-8';
@@ -22,7 +24,13 @@ export class AmountComponent implements OnInit, OnDestroy {
constructor(
private stateService: StateService,
) { }
private cd: ChangeDetectorRef,
) {
this.currencySubscription = this.stateService.fiatCurrency$.subscribe((fiat) => {
this.currency = fiat;
this.cd.markForCheck();
});
}
ngOnInit() {
this.viewFiat$ = this.stateService.viewFiat$.asObservable();
@@ -34,6 +42,7 @@ export class AmountComponent implements OnInit, OnDestroy {
if (this.stateSubscription) {
this.stateSubscription.unsubscribe();
}
this.currencySubscription.unsubscribe();
}
}

View File

@@ -1,16 +1,19 @@
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
import { EChartsOption, graphic } from 'echarts';
import { Observable } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
import { ApiService } from '../../services/api.service';
import { SeoService } from '../../services/seo.service';
import { formatCurrency, formatNumber, getCurrencySymbol } from '@angular/common';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from '../../shared/graphs.utils';
import { StateService } from '../../services/state.service';
import { StorageService } from '../../services/storage.service';
import { MiningService } from '../../services/mining.service';
import { ActivatedRoute } from '@angular/router';
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
import { FiatCurrencyPipe } from '../../shared/pipes/fiat-currency.pipe';
import { fiatCurrencies } from '../../app.constants';
@Component({
selector: 'app-block-fees-graph',
@@ -44,6 +47,9 @@ export class BlockFeesGraphComponent implements OnInit {
timespan = '';
chartInstance: any = undefined;
currencySubscription: Subscription;
currency: string;
constructor(
@Inject(LOCALE_ID) public locale: string,
private seoService: SeoService,
@@ -51,11 +57,21 @@ export class BlockFeesGraphComponent implements OnInit {
private formBuilder: UntypedFormBuilder,
private storageService: StorageService,
private miningService: MiningService,
private stateService: StateService,
private route: ActivatedRoute,
private fiatShortenerPipe: FiatShortenerPipe,
private fiatCurrencyPipe: FiatCurrencyPipe,
) {
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
this.radioGroupForm.controls.dateSpan.setValue('1y');
this.currencySubscription = this.stateService.fiatCurrency$.subscribe((fiat) => {
if (fiat && fiatCurrencies[fiat]?.indexed) {
this.currency = fiat;
} else {
this.currency = 'USD';
}
});
}
ngOnInit(): void {
@@ -84,7 +100,7 @@ export class BlockFeesGraphComponent implements OnInit {
tap((response) => {
this.prepareChartOptions({
blockFees: response.body.map(val => [val.timestamp * 1000, val.avgFees / 100000000, val.avgHeight]),
blockFeesUSD: response.body.filter(val => val.USD > 0).map(val => [val.timestamp * 1000, val.avgFees / 100000000 * val.USD, val.avgHeight]),
blockFeesFiat: response.body.filter(val => val[this.currency] > 0).map(val => [val.timestamp * 1000, val.avgFees / 100000000 * val[this.currency], val.avgHeight]),
});
this.isLoading = false;
}),
@@ -157,7 +173,7 @@ export class BlockFeesGraphComponent implements OnInit {
if (tick.seriesIndex === 0) {
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.3-3')} BTC<br>`;
} else if (tick.seriesIndex === 1) {
tooltip += `${tick.marker} ${tick.seriesName}: ${formatCurrency(tick.data[1], this.locale, getCurrencySymbol('USD', 'narrow'), 'USD', '1.0-0')}<br>`;
tooltip += `${tick.marker} ${tick.seriesName}: ${this.fiatCurrencyPipe.transform(tick.data[1], null, this.currency) }<br>`;
}
}
@@ -184,7 +200,7 @@ export class BlockFeesGraphComponent implements OnInit {
icon: 'roundRect',
},
{
name: 'Fees USD',
name: 'Fees ' + this.currency,
inactiveColor: 'rgb(110, 112, 121)',
textStyle: {
color: 'white',
@@ -216,7 +232,7 @@ export class BlockFeesGraphComponent implements OnInit {
axisLabel: {
color: 'rgb(110, 112, 121)',
formatter: function(val) {
return this.fiatShortenerPipe.transform(val);
return this.fiatShortenerPipe.transform(val, null, this.currency);
}.bind(this)
},
splitLine: {
@@ -243,8 +259,8 @@ export class BlockFeesGraphComponent implements OnInit {
legendHoverLink: false,
zlevel: 1,
yAxisIndex: 1,
name: 'Fees USD',
data: data.blockFeesUSD,
name: 'Fees ' + this.currency,
data: data.blockFeesFiat,
type: 'line',
smooth: 0.25,
symbol: 'none',

View File

@@ -1,16 +1,19 @@
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
import { EChartsOption, graphic } from 'echarts';
import { Observable } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
import { ApiService } from '../../services/api.service';
import { SeoService } from '../../services/seo.service';
import { formatCurrency, formatNumber, getCurrencySymbol } from '@angular/common';
import { formatNumber } from '@angular/common';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from '../../shared/graphs.utils';
import { MiningService } from '../../services/mining.service';
import { StateService } from '../../services/state.service';
import { StorageService } from '../../services/storage.service';
import { ActivatedRoute } from '@angular/router';
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
import { FiatCurrencyPipe } from '../../shared/pipes/fiat-currency.pipe';
import { fiatCurrencies } from '../../app.constants';
@Component({
selector: 'app-block-rewards-graph',
@@ -44,16 +47,28 @@ export class BlockRewardsGraphComponent implements OnInit {
timespan = '';
chartInstance: any = undefined;
currencySubscription: Subscription;
currency: string;
constructor(
@Inject(LOCALE_ID) public locale: string,
private seoService: SeoService,
private apiService: ApiService,
private formBuilder: UntypedFormBuilder,
private miningService: MiningService,
private stateService: StateService,
private storageService: StorageService,
private route: ActivatedRoute,
private fiatShortenerPipe: FiatShortenerPipe,
private fiatCurrencyPipe: FiatCurrencyPipe,
) {
this.currencySubscription = this.stateService.fiatCurrency$.subscribe((fiat) => {
if (fiat && fiatCurrencies[fiat]?.indexed) {
this.currency = fiat;
} else {
this.currency = 'USD';
}
});
}
ngOnInit(): void {
@@ -82,7 +97,7 @@ export class BlockRewardsGraphComponent implements OnInit {
tap((response) => {
this.prepareChartOptions({
blockRewards: response.body.map(val => [val.timestamp * 1000, val.avgRewards / 100000000, val.avgHeight]),
blockRewardsUSD: response.body.filter(val => val.USD > 0).map(val => [val.timestamp * 1000, val.avgRewards / 100000000 * val.USD, val.avgHeight]),
blockRewardsFiat: response.body.filter(val => val[this.currency] > 0).map(val => [val.timestamp * 1000, val.avgRewards / 100000000 * val[this.currency], val.avgHeight]),
});
this.isLoading = false;
}),
@@ -157,7 +172,7 @@ export class BlockRewardsGraphComponent implements OnInit {
if (tick.seriesIndex === 0) {
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.3-3')} BTC<br>`;
} else if (tick.seriesIndex === 1) {
tooltip += `${tick.marker} ${tick.seriesName}: ${formatCurrency(tick.data[1], this.locale, getCurrencySymbol('USD', 'narrow'), 'USD', '1.0-0')}<br>`;
tooltip += `${tick.marker} ${tick.seriesName}: ${this.fiatCurrencyPipe.transform(tick.data[1], null, this.currency)}<br>`;
}
}
@@ -184,7 +199,7 @@ export class BlockRewardsGraphComponent implements OnInit {
icon: 'roundRect',
},
{
name: 'Rewards USD',
name: 'Rewards ' + this.currency,
inactiveColor: 'rgb(110, 112, 121)',
textStyle: {
color: 'white',
@@ -228,7 +243,7 @@ export class BlockRewardsGraphComponent implements OnInit {
axisLabel: {
color: 'rgb(110, 112, 121)',
formatter: function(val) {
return this.fiatShortenerPipe.transform(val);
return this.fiatShortenerPipe.transform(val, null, this.currency);
}.bind(this)
},
splitLine: {
@@ -251,8 +266,8 @@ export class BlockRewardsGraphComponent implements OnInit {
legendHoverLink: false,
zlevel: 1,
yAxisIndex: 1,
name: 'Rewards USD',
data: data.blockRewardsUSD,
name: 'Rewards ' + this.currency,
data: data.blockRewardsFiat,
type: 'line',
smooth: 0.25,
symbol: 'none',

View File

@@ -90,6 +90,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
this.prepareChartOptions({
sizes: data.sizes.map(val => [val.timestamp * 1000, val.avgSize / 1000000, val.avgHeight]),
weights: data.weights.map(val => [val.timestamp * 1000, val.avgWeight / 1000000, val.avgHeight]),
sizePerWeight: data.weights.map((val, i) => [val.timestamp * 1000, data.sizes[i].avgSize / (val.avgWeight / 4), val.avgHeight]),
});
this.isLoading = false;
}),
@@ -124,6 +125,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
color: [
'#FDD835',
'#D81B60',
'#039BE5',
],
grid: {
top: 30,
@@ -153,6 +155,8 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.2-2')} MB`;
} else if (tick.seriesIndex === 1) { // Weight
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.2-2')} MWU`;
} else if (tick.seriesIndex === 2) { // Size per weight
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.2-2')} B/vB`;
}
tooltip += `<br>`;
}
@@ -192,10 +196,19 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
},
icon: 'roundRect',
},
{
name: $localize`Size per weight`,
inactiveColor: 'rgb(110, 112, 121)',
textStyle: {
color: 'white',
},
icon: 'roundRect',
},
],
selected: JSON.parse(this.storageService.getValue('sizes_weights_legend')) ?? {
'Size': true,
'Weight': true,
'Size per weight': true,
}
},
yAxis: data.sizes.length === 0 ? undefined : [
@@ -262,6 +275,18 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
lineStyle: {
width: 2,
}
},
{
zlevel: 1,
yAxisIndex: 0,
name: $localize`Size per weight`,
showSymbol: false,
symbol: 'none',
data: data.sizePerWeight,
type: 'line',
lineStyle: {
width: 2,
}
}
],
dataZoom: [{

View File

@@ -53,7 +53,7 @@
<td [innerHTML]="'&lrm;' + (block.weight | wuBytes: 2)"></td>
</tr>
<tr *ngIf="auditAvailable">
<td i18n="latest-blocks.health">Health</td>
<td><ng-container i18n="latest-blocks.health">Health</ng-container> <a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="what-is-block-health"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></td>
<td>
<span
class="health-badge badge"
@@ -143,7 +143,7 @@
</ng-template>
</tr>
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees</td>
<td>
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
<span class="fiat">
@@ -158,7 +158,7 @@
<td style="width: 75%;"><span class="skeleton-loader"></span></td>
</tr>
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees</td>
<td><span class="skeleton-loader"></span></td>
</tr>
</ng-template>
@@ -206,13 +206,13 @@
<div class="box" *ngIf="!error && webGlEnabled && showAudit">
<div class="nav nav-tabs" *ngIf="isMobile && showAudit">
<a class="nav-link" [class.active]="mode === 'projected'"
fragment="projected" (click)="changeMode('projected')"><ng-container i18n="block.projected">Projected</ng-container>&nbsp;&nbsp;<span class="badge badge-pill badge-warning" i18n="beta">beta</span></a>
fragment="projected" (click)="changeMode('projected')"><ng-container i18n="block.expected">Expected</ng-container>&nbsp;&nbsp;<span class="badge badge-pill badge-warning" i18n="beta">beta</span></a>
<a class="nav-link" [class.active]="mode === 'actual'" i18n="block.actual"
fragment="actual" (click)="changeMode('actual')">Actual</a>
</div>
<div class="row">
<div class="col-sm">
<h3 class="block-subtitle" *ngIf="!isMobile"><ng-container i18n="block.projected-block">Projected Block</ng-container> <span class="badge badge-pill badge-warning beta" i18n="beta">beta</span></h3>
<h3 class="block-subtitle" *ngIf="!isMobile"><ng-container i18n="block.expected-block">Expected Block</ng-container> <span class="badge badge-pill badge-warning beta" i18n="beta">beta</span></h3>
<div class="block-graph-wrapper">
<app-block-overview-graph #blockGraphProjected [isLoading]="isLoadingOverview" [resolution]="75"
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" [auditHighlighting]="showAudit"
@@ -221,7 +221,7 @@
</div>
</div>
<div class="col-sm" *ngIf="!isMobile">
<h3 class="block-subtitle" *ngIf="!isMobile" i18n="block.actual-block">Actual Block</h3>
<h3 class="block-subtitle actual" *ngIf="!isMobile"><ng-container i18n="block.actual-block">Actual Block</ng-container> <a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="how-do-block-audits-work"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></h3>
<div class="block-graph-wrapper">
<app-block-overview-graph #blockGraphActual [isLoading]="isLoadingOverview" [resolution]="75"
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined" [auditHighlighting]="showAudit"
@@ -231,7 +231,7 @@
</div>
</div>
</div>
<ng-template [ngIf]="!isLoadingBlock && !error">
<div [hidden]="!showDetails" id="details">
<br>

View File

@@ -34,9 +34,24 @@
text-align: left;
}
}
.info-link {
color: rgba(255, 255, 255, 0.4);
margin-left: 5px;
}
}
}
.block-subtitle.actual a {
position: absolute;
top: -3px;
}
.block-subtitle.actual fa-icon {
color: rgba(255, 255, 255, 0.4);
font-size: 18px;
margin-left: 8px;
}
h1 {
margin: 0px;
padding: 0px;

View File

@@ -57,7 +57,7 @@ export class BlockComponent implements OnInit, OnDestroy {
transactionsError: any = null;
overviewError: any = null;
webGlEnabled = true;
indexingAvailable = false;
auditSupported: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true;
auditModeEnabled: boolean = !this.stateService.hideAudit.value;
auditAvailable = true;
showAudit: boolean;
@@ -109,13 +109,14 @@ export class BlockComponent implements OnInit, OnDestroy {
this.timeLtr = !!ltr;
});
this.indexingAvailable = (this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true);
this.setAuditAvailable(this.indexingAvailable);
this.setAuditAvailable(this.auditSupported);
this.auditPrefSubscription = this.stateService.hideAudit.subscribe((hide) => {
this.auditModeEnabled = !hide;
this.showAudit = this.auditAvailable && this.auditModeEnabled;
});
if (this.auditSupported) {
this.auditPrefSubscription = this.stateService.hideAudit.subscribe((hide) => {
this.auditModeEnabled = !hide;
this.showAudit = this.auditAvailable && this.auditModeEnabled;
});
}
this.txsLoadingStatus$ = this.route.paramMap
.pipe(
@@ -221,7 +222,9 @@ export class BlockComponent implements OnInit, OnDestroy {
setTimeout(() => {
this.nextBlockSubscription = this.apiService.getBlock$(block.previousblockhash).subscribe();
this.nextBlockTxListSubscription = this.electrsApiService.getBlockTransactions$(block.previousblockhash).subscribe();
this.apiService.getBlockAudit$(block.previousblockhash);
if (this.auditSupported) {
this.apiService.getBlockAudit$(block.previousblockhash);
}
}, 100);
}
this.updateAuditAvailableFromBlockHeight(block.height);
@@ -269,7 +272,7 @@ export class BlockComponent implements OnInit, OnDestroy {
this.isLoadingOverview = false;
});
if (!this.indexingAvailable) {
if (!this.auditSupported) {
this.overviewSubscription = block$.pipe(
startWith(null),
pairwise(),
@@ -300,7 +303,7 @@ export class BlockComponent implements OnInit, OnDestroy {
});
}
if (this.indexingAvailable) {
if (this.auditSupported) {
this.auditSubscription = block$.pipe(
startWith(null),
pairwise(),
@@ -605,7 +608,7 @@ export class BlockComponent implements OnInit, OnDestroy {
setAuditAvailable(available: boolean): void {
this.auditAvailable = available;
this.showAudit = this.auditAvailable && this.auditModeEnabled;
this.showAudit = this.auditAvailable && this.auditModeEnabled && this.auditSupported;
}
toggleAuditMode(): void {
@@ -613,6 +616,9 @@ export class BlockComponent implements OnInit, OnDestroy {
}
updateAuditAvailableFromBlockHeight(blockHeight: number): void {
if (!this.auditSupported) {
this.setAuditAvailable(false);
}
switch (this.stateService.network) {
case 'testnet':
if (blockHeight < this.stateService.env.TESTNET_BLOCK_AUDIT_START_HEIGHT) {

View File

@@ -1,64 +1,86 @@
<div class="blocks-container blockchain-blocks-container" [class.time-ltr]="timeLtr" [style.left]="static ? (offset || 0) + 'px' : null" *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
<div class="blocks-container blockchain-blocks-container" [class.time-ltr]="timeLtr"
[style.left]="static ? (offset || 0) + 'px' : null"
*ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
<div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn">
<ng-container *ngIf="block && !block.loading && !block.placeholder; else placeholderBlock">
<div [attr.data-cy]="'bitcoin-block-' + i" class="text-center bitcoin-block mined-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]" [class.blink-bg]="(specialBlocks[block.height] !== undefined)">
<div [attr.data-cy]="'bitcoin-block-offset-' + offset + '-index-' + i"
class="text-center bitcoin-block mined-block blockchain-blocks-offset-{{ offset }}-index-{{ i }}"
id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]"
[class.blink-bg]="(specialBlocks[block.height] !== undefined)">
<a draggable="false" [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }"
class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}">&nbsp;</a>
<div [attr.data-cy]="'bitcoin-block-' + i + '-height'" class="block-height">
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height
}}</a>
</div>
<div class="block-body">
<div [attr.data-cy]="'bitcoin-block-' + i + '-fees'" class="fees">
~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
<div [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees">
~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container
i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
</div>
<div [attr.data-cy]="'bitcoin-block-' + i + '-fee-span'" class="fee-span" *ngIf="block?.extras?.feeRange">
{{ block?.extras?.feeRange?.[1] | number:feeRounding }} - {{ block?.extras?.feeRange[block?.extras?.feeRange?.length - 1] | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-fee-span'" class="fee-span"
*ngIf="block?.extras?.feeRange">
{{ block?.extras?.feeRange?.[1] | number:feeRounding }} - {{
block?.extras?.feeRange[block?.extras?.feeRange?.length - 1] | number:feeRounding }} <ng-container
i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
</div>
<div [attr.data-cy]="'bitcoin-block-' + i + '-fee-span'" class="fee-span" *ngIf="!block?.extras?.feeRange">
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-fee-span'" class="fee-span"
*ngIf="!block?.extras?.feeRange">
&nbsp;
</div>
<div [attr.data-cy]="'bitcoin-block-' + i + '-total-fees'" *ngIf="showMiningInfo" class="block-size">
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-total-fees'" *ngIf="showMiningInfo"
class="block-size">
<app-amount [satoshis]="block.extras?.totalFees ?? 0" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
</div>
<div [attr.data-cy]="'bitcoin-block-' + i + 'block-size'" *ngIf="!showMiningInfo" class="block-size" [innerHTML]="'&lrm;' + (block.size | bytes: 2)"></div>
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + 'block-size'" *ngIf="!showMiningInfo"
class="block-size" [innerHTML]="'&lrm;' + (block.size | bytes: 2)"></div>
<div [attr.data-cy]="'bitcoin-block-' + i + '-transactions'" class="transaction-count">
<ng-container *ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container>
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
<ng-container
*ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container>
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }}
transaction</ng-template>
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }}
transactions</ng-template>
</div>
<div [attr.data-cy]="'bitcoin-block-' + i + '-time'" class="time-difference"><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since></div>
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-time'" class="time-difference">
<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since></div>
</div>
<div class="animated" [class]="showMiningInfo ? 'show' : 'hide'" *ngIf="block.extras?.pool != undefined">
<a [attr.data-cy]="'bitcoin-block-' + i + '-pool'" class="badge badge-primary" [routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
<a [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-pool'" class="badge badge-primary"
[routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
{{ block.extras.pool.name}}</a>
</div>
</div>
</ng-container>
<ng-template #placeholderBlock>
<ng-container *ngIf="block && block.placeholder; else loadingBlock">
<div [attr.data-cy]="'bitcoin-block-' + i" class="text-center bitcoin-block mined-block placeholder-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]">
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i"
class="text-center bitcoin-block mined-block placeholder-block blockchain-blocks-{{ i }}"
id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]">
</div>
</ng-container>
</ng-template>
<ng-template #loadingBlock>
<ng-container *ngIf="block && block.loading">
<div class="flashing">
<div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]"></div>
<div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}"
[ngStyle]="blockStyles[i]"></div>
</div>
</ng-container>
</ng-template>
</div>
<div [hidden]="!arrowVisible" id="arrow-up" [style.transition]="arrowTransition" [ngStyle]="{'left': arrowLeftPx + 'px' }"></div>
<div [hidden]="!arrowVisible" id="arrow-up" [style.transition]="arrowTransition"
[ngStyle]="{'left': arrowLeftPx + 'px' }"></div>
</div>
<ng-template #loadingBlocksTemplate>
<div class="blocks-container" [class.time-ltr]="timeLtr">
<div class="flashing">
<div *ngFor="let block of emptyBlocks; let i = index; trackBy: trackByBlocksFn" >
<div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" [ngStyle]="emptyBlockStyles[i]"></div>
<div *ngFor="let block of emptyBlocks; let i = index; trackBy: trackByBlocksFn">
<div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}"
[ngStyle]="emptyBlockStyles[i]"></div>
</div>
</div>
</div>
</ng-template>

View File

@@ -14,7 +14,7 @@
i18n-ngbTooltip="mining.pool-name" ngbTooltip="Pool" placement="bottom" #miningpool [disableTooltip]="!isEllipsisActive(miningpool)">Pool</th>
<th class="timestamp" i18n="latest-blocks.timestamp" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">Timestamp</th>
<th class="mined" i18n="latest-blocks.mined" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">Mined</th>
<th *ngIf="indexingAvailable" class="health text-right" i18n="latest-blocks.health" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
<th *ngIf="auditAvailable" class="health text-right" i18n="latest-blocks.health" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
i18n-ngbTooltip="latest-blocks.health" ngbTooltip="Health" placement="bottom" #health [disableTooltip]="!isEllipsisActive(health)">Health</th>
<th *ngIf="indexingAvailable" class="reward text-right" i18n="latest-blocks.reward" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
i18n-ngbTooltip="latest-blocks.reward" ngbTooltip="Reward" placement="bottom" #reward [disableTooltip]="!isEllipsisActive(reward)">Reward</th>
@@ -45,7 +45,7 @@
<td class="mined" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">
<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since>
</td>
<td *ngIf="indexingAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
<td *ngIf="auditAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
<a
class="health-badge badge"
[class.badge-success]="auditScores[block.id] >= 99"
@@ -98,7 +98,7 @@
<td class="mined" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">
<span class="skeleton-loader" style="max-width: 125px"></span>
</td>
<td *ngIf="indexingAvailable" class="health text-left" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
<td *ngIf="auditAvailable" class="health text-left" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
<span class="skeleton-loader" style="max-width: 75px"></span>
</td>
<td *ngIf="indexingAvailable" class="reward text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
@@ -123,8 +123,10 @@
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
</ngb-pagination>
<div class="clearfix"></div>
<br>
<ng-template [ngIf]="!widget">
<div class="clearfix"></div>
<br>
</ng-template>
</div>
</div>

View File

@@ -22,6 +22,7 @@ export class BlocksList implements OnInit, OnDestroy {
latestScoreSubscription: Subscription;
indexingAvailable = false;
auditAvailable = false;
isLoading = true;
loadingScores = true;
fromBlockHeight = undefined;
@@ -44,6 +45,7 @@ export class BlocksList implements OnInit, OnDestroy {
ngOnInit(): void {
this.indexingAvailable = (this.stateService.env.BASE_MODULE === 'mempool' &&
this.stateService.env.MINING_DASHBOARD === true);
this.auditAvailable = this.indexingAvailable && this.stateService.env.AUDIT;
if (!this.widget) {
this.websocketService.want(['blocks']);
@@ -111,7 +113,7 @@ export class BlocksList implements OnInit, OnDestroy {
}, [])
);
if (this.indexingAvailable) {
if (this.indexingAvailable && this.auditAvailable) {
this.auditScoreSubscription = this.fromHeightSubject.pipe(
switchMap((fromBlockHeight) => {
this.loadingScores = true;

View File

@@ -0,0 +1,5 @@
<div [formGroup]="fiatForm" class="text-small text-center">
<select formControlName="fiat" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" style="width: 200px;" (change)="changeFiat()">
<option *ngFor="let currency of currencies" [value]="currency[1].code">{{ currency[1].name + " (" + currency[1].code + ")" }}</option>
</select>
</div>

View File

@@ -0,0 +1,45 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { StorageService } from '../../services/storage.service';
import { fiatCurrencies } from '../../app.constants';
import { StateService } from '../../services/state.service';
@Component({
selector: 'app-fiat-selector',
templateUrl: './fiat-selector.component.html',
styleUrls: ['./fiat-selector.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FiatSelectorComponent implements OnInit {
fiatForm: UntypedFormGroup;
currencies = Object.entries(fiatCurrencies).sort((a: any, b: any) => {
if (a[1].name < b[1].name) {
return -1;
}
if (a[1].name > b[1].name) {
return 1;
}
return 0;
});
constructor(
private formBuilder: UntypedFormBuilder,
private stateService: StateService,
private storageService: StorageService,
) { }
ngOnInit() {
this.fiatForm = this.formBuilder.group({
fiat: ['USD']
});
this.stateService.fiatCurrency$.subscribe((fiat) => {
this.fiatForm.get('fiat')?.setValue(fiat);
});
}
changeFiat() {
const newFiat = this.fiatForm.get('fiat')?.value;
this.storageService.setValue('fiat-preference', newFiat);
this.stateService.fiatCurrency$.next(newFiat);
}
}

View File

@@ -22,7 +22,7 @@
i18n="mining.block-rewards">Block Rewards</a>
<a class="dropdown-item" routerLinkActive="active"
[routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]" i18n="mining.block-sizes-weights">Block Sizes and Weights</a>
<a class="dropdown-item" routerLinkActive="active"
<a *ngIf="stateService.env.AUDIT" class="dropdown-item" routerLinkActive="active"
[routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" i18n="mining.block-prediction-accuracy">Block Prediction Accuracy</a>
</div>
</div>

View File

@@ -1,5 +1,5 @@
<div [formGroup]="languageForm" class="text-small text-center">
<select formControlName="language" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" style="width: 130px;" (change)="changeLanguage()">
<select formControlName="language" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" style="width: 200px;" (change)="changeLanguage()">
<option *ngFor="let lang of languages" [value]="lang.code">{{ lang.name }}</option>
</select>
</div>

View File

@@ -74,4 +74,24 @@
</div>
</div>
<div class="pref-selectors">
<div class="selector">
<app-language-selector></app-language-selector>
</div>
<div class="selector">
<app-fiat-selector></app-fiat-selector>
</div>
</div>
<div class="terms-of-service">
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
<a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a>
|
<a [routerLink]="['/tx/push' | relativeUrl]" i18n="shared.broadcast-transaction|Broadcast Transaction">Broadcast Transaction</a>
</div>
<br>
</div>

View File

@@ -103,4 +103,23 @@
margin-bottom: 10px;
text-decoration: none;
color: inherit;
}
.terms-of-service {
margin-top: 1rem;
}
.pref-selectors {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
.selector {
margin-left: .5em;
margin-bottom: .5em;
&:first {
margin-left: 0;
}
}
}

View File

@@ -176,7 +176,7 @@ export class PoolRankingComponent implements OnInit {
// 'Other'
data.push({
itemStyle: {
color: 'grey',
color: '#6b6b6b',
},
value: totalShareOther,
name: 'Other' + (isMobile() ? `` : ` (${totalShareOther.toFixed(2)}%)`),

View File

@@ -235,10 +235,10 @@
</span>
</td>
<td class="reward text-right">
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-2"></app-amount>
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-2" [noFiat]="true"></app-amount>
</td>
<td class="fees text-right">
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-2"></app-amount>
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-2" [noFiat]="true"></app-amount>
</td>
<td class="txs text-right">
{{ block.tx_count | number }}

View File

@@ -17,7 +17,7 @@
<app-blockchain [pageIndex]="pageIndex" [pages]="pages" [blocksPerPage]="blocksPerPage" [minScrollWidth]="minScrollWidth"></app-blockchain>
</div>
<div class="reset-scroll" [class.hidden]="pageIndex === 0" (click)="resetScroll()">
<fa-icon [icon]="['fas', 'circle-left']" [fixedWidth]="true" i18n-title="blocks.return-to-tip" title="Return to tip"></fa-icon>
<fa-icon [icon]="['fas', 'circle-left']" [fixedWidth]="true"></fa-icon>
</div>
</div>

View File

@@ -300,6 +300,10 @@ export class StartComponent implements OnInit, OnDestroy {
}
ngOnDestroy() {
if (this.blockchainContainer?.nativeElement) {
// clean up scroll position to prevent caching wrong scroll in Firefox
this.blockchainContainer.nativeElement.scrollLeft = 0;
}
this.timeLtrSubscription.unsubscribe();
this.chainTipSubscription.unsubscribe();
this.markBlockSubscription.unsubscribe();