Merge branch 'master' into desktop-refinements
This commit is contained in:
@@ -31,6 +31,7 @@ import { MiningDashboardComponent } from './components/mining-dashboard/mining-d
|
||||
import { HashrateChartComponent } from './components/hashrate-chart/hashrate-chart.component';
|
||||
import { HashrateChartPoolsComponent } from './components/hashrates-chart-pools/hashrate-chart-pools.component';
|
||||
import { MiningStartComponent } from './components/mining-start/mining-start.component';
|
||||
import { BlocksList } from './components/blocks-list/blocks-list.component';
|
||||
|
||||
let routes: Routes = [
|
||||
{
|
||||
@@ -75,6 +76,10 @@ let routes: Routes = [
|
||||
path: 'mining',
|
||||
component: MiningStartComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'blocks',
|
||||
component: BlocksList,
|
||||
},
|
||||
{
|
||||
path: 'hashrate',
|
||||
component: HashrateChartComponent,
|
||||
@@ -190,6 +195,10 @@ let routes: Routes = [
|
||||
path: 'mining',
|
||||
component: MiningStartComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'blocks',
|
||||
component: BlocksList,
|
||||
},
|
||||
{
|
||||
path: 'hashrate',
|
||||
component: HashrateChartComponent,
|
||||
@@ -299,6 +308,10 @@ let routes: Routes = [
|
||||
path: 'mining',
|
||||
component: MiningStartComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'blocks',
|
||||
component: BlocksList,
|
||||
},
|
||||
{
|
||||
path: 'hashrate',
|
||||
component: HashrateChartComponent,
|
||||
@@ -630,7 +643,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
||||
initialNavigation: 'enabled',
|
||||
scrollPositionRestoration: 'enabled',
|
||||
anchorScrolling: 'enabled'
|
||||
})],
|
||||
})],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
||||
|
||||
@@ -76,6 +76,7 @@ import { MiningStartComponent } from './components/mining-start/mining-start.com
|
||||
import { AmountShortenerPipe } from './shared/pipes/amount-shortener.pipe';
|
||||
import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
|
||||
import { DifficultyAdjustmentsTable } from './components/difficulty-adjustments-table/difficulty-adjustments-table.components';
|
||||
import { BlocksList } from './components/blocks-list/blocks-list.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -133,6 +134,7 @@ import { DifficultyAdjustmentsTable } from './components/difficulty-adjustments-
|
||||
MiningStartComponent,
|
||||
AmountShortenerPipe,
|
||||
DifficultyAdjustmentsTable,
|
||||
BlocksList,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
||||
|
||||
@@ -170,13 +170,8 @@
|
||||
</div>
|
||||
|
||||
<div class="community-integrations-sponsor">
|
||||
<h3 i18n="about.integrations">Community Integrations</h3>
|
||||
|
||||
<h3 i18n="about.self-hosted-integrations">Self-Hosted Integrations</h3>
|
||||
<div class="wrapper">
|
||||
<a href="https://github.com/bisq-network/bisq" target="_blank" title="Bisq">
|
||||
<img class="image" src="/resources/profile/bisq_network.png" />
|
||||
<span>Bisq</span>
|
||||
</a>
|
||||
<a href="https://github.com/getumbrel/umbrel" target="_blank" title="Umbrel">
|
||||
<img class="image" src="/resources/profile/umbrel.png" />
|
||||
<span>Umbrel</span>
|
||||
@@ -201,6 +196,20 @@
|
||||
<img class="image" src="/resources/profile/nix-bitcoin.png" />
|
||||
<span>NixOS</span>
|
||||
</a>
|
||||
<a href="https://github.com/Start9Labs/embassy-os" target="_blank" title="EmbassyOS">
|
||||
<img class="image" src="/resources/profile/start9.png" />
|
||||
<span>EmbassyOS</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="community-integrations-sponsor">
|
||||
<h3 i18n="about.wallet-integrations">Wallet Integrations</h3>
|
||||
<div class="wrapper">
|
||||
<a href="https://github.com/bisq-network/bisq" target="_blank" title="Bisq">
|
||||
<img class="image" src="/resources/profile/bisq_network.png" />
|
||||
<span>Bisq</span>
|
||||
</a>
|
||||
<a href="https://github.com/spesmilo/electrum" target="_blank" title="Electrum Wallet">
|
||||
<img class="image" src="/resources/profile/electrum.jpg" />
|
||||
<span>Electrum</span>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<app-transactions-list [transactions]="transactions" [showConfirmations]="true" (loadMore)="loadMore()"></app-transactions-list>
|
||||
<app-transactions-list [transactions]="transactions" [showConfirmations]="true" [address]="address.address" (loadMore)="loadMore()"></app-transactions-list>
|
||||
|
||||
<div class="text-center">
|
||||
<ng-template [ngIf]="isLoadingTransactions">
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<ng-container *ngIf="!noFiat && (viewFiat$ | async) && (conversions$ | async) as conversions; else viewFiatVin">
|
||||
<span class="fiat">{{ conversions.USD * (satoshis / 100000000) | currency:'USD':'symbol':'1.2-2' }}</span>
|
||||
<span class="fiat">{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ conversions.USD * (satoshis / 100000000) | currency:'USD':'symbol':'1.2-2' }}</span>
|
||||
</ng-container>
|
||||
<ng-template #viewFiatVin>
|
||||
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
|
||||
<span i18n="shared.confidential">Confidential</span>
|
||||
</ng-template>
|
||||
<ng-template #default>
|
||||
‎{{ satoshis / 100000000 | number : digitsInfo }}
|
||||
‎{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis / 100000000 | number : digitsInfo }}
|
||||
<span class="symbol"><ng-template [ngIf]="network === 'liquid'">L-</ng-template>
|
||||
<ng-template [ngIf]="network === 'liquidtestnet'">tL-</ng-template>
|
||||
<ng-template [ngIf]="network === 'testnet'">t</ng-template>
|
||||
|
||||
@@ -18,6 +18,7 @@ export class AmountComponent implements OnInit, OnDestroy {
|
||||
@Input() satoshis: number;
|
||||
@Input() digitsInfo = '1.8-8';
|
||||
@Input() noFiat = false;
|
||||
@Input() addPlus = false;
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<div class="container-xl" [class]="widget ? 'widget' : ''">
|
||||
<h1 *ngIf="!widget" class="float-left" i18n="latest-blocks.blocks">Blocks</h1>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div style="min-height: 295px">
|
||||
<table class="table table-borderless">
|
||||
<thead>
|
||||
<th class="height" [class]="widget ? 'widget' : ''" i18n="latest-blocks.height">Height</th>
|
||||
<th class="pool text-left" [class]="widget ? 'widget' : ''" i18n="latest-blocks.mined-by">
|
||||
Pool</th>
|
||||
<th class="timestamp" i18n="latest-blocks.timestamp" *ngIf="!widget">Timestamp</th>
|
||||
<th class="mined" i18n="latest-blocks.mined" *ngIf="!widget">Mined</th>
|
||||
<th class="reward text-right" i18n="latest-blocks.reward" [class]="widget ? 'widget' : ''">
|
||||
Reward</th>
|
||||
<th class="fees text-right" i18n="latest-blocks.fees" *ngIf="!widget">Fees</th>
|
||||
<th class="txs text-right" i18n="latest-blocks.transactions" [class]="widget ? 'widget' : ''">Txs</th>
|
||||
<th class="size" i18n="latest-blocks.size" *ngIf="!widget">Size</th>
|
||||
</thead>
|
||||
<tbody *ngIf="blocks$ | async as blocks; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||
<tr *ngFor="let block of blocks; let i= index; trackBy: trackByBlock">
|
||||
<td class="height " [class]="widget ? 'widget' : ''">
|
||||
<a [routerLink]="['/block' | relativeUrl, block.height]">{{ block.height
|
||||
}}</a>
|
||||
</td>
|
||||
<td class="pool text-left" [class]="widget ? 'widget' : ''">
|
||||
<a class="clear-link" [routerLink]="[('/mining/pool/' + block.extras.pool.id) | relativeUrl]">
|
||||
<img width="25" height="25" src="{{ block.extras.pool['logo'] }}"
|
||||
onError="this.src = './resources/mining-pools/default.svg'">
|
||||
<span class="pool-name">{{ block.extras.pool.name }}</span>
|
||||
</a>
|
||||
</td>
|
||||
<td class="timestamp" *ngIf="!widget">
|
||||
‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}
|
||||
</td>
|
||||
<td class="mined" *ngIf="!widget">
|
||||
<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since>
|
||||
</td>
|
||||
<td class="reward text-right" [class]="widget ? 'widget' : ''">
|
||||
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-2"></app-amount>
|
||||
</td>
|
||||
<td class="fees text-right" *ngIf="!widget">
|
||||
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-2"></app-amount>
|
||||
</td>
|
||||
<td class="txs text-right" [class]="widget ? 'widget' : ''">
|
||||
{{ block.tx_count | number }}
|
||||
</td>
|
||||
<td class="size" *ngIf="!widget">
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-mempool" role="progressbar"
|
||||
[ngStyle]="{'width': (block.weight / stateService.env.BLOCK_WEIGHT_UNITS)*100 + '%' }"></div>
|
||||
<div class="progress-text" [innerHTML]="block.size | bytes: 2"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<ng-template #skeleton>
|
||||
<tbody>
|
||||
<tr *ngFor="let item of skeletonLines">
|
||||
<td class="height" [class]="widget ? 'widget' : ''">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="pool text-left" [class]="widget ? 'widget' : ''">
|
||||
<img width="0" height="25" style="opacity: 0">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="timestamp" *ngIf="!widget">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="mined" *ngIf="!widget">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="reward text-right" [class]="widget ? 'widget' : ''">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="fees text-right" *ngIf="!widget">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="txs text-right" [class]="widget ? 'widget' : ''">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="size" *ngIf="!widget">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</ng-template>
|
||||
</table>
|
||||
|
||||
<ngb-pagination *ngIf="!widget" class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''"
|
||||
[collectionSize]="blocksCount" [rotate]="true" [maxSize]="5" [pageSize]="15" [(page)]="page"
|
||||
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
|
||||
</ngb-pagination>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,124 @@
|
||||
.container-xl {
|
||||
max-width: 1400px;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
.container-xl.widget {
|
||||
padding-left: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
td {
|
||||
padding-top: 0.7rem !important;
|
||||
padding-bottom: 0.7rem !important;
|
||||
}
|
||||
|
||||
.clear-link {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.progress {
|
||||
background-color: #2d3348;
|
||||
}
|
||||
|
||||
.pool {
|
||||
width: 17%;
|
||||
}
|
||||
.pool.widget {
|
||||
width: 40%;
|
||||
@media (max-width: 576px) {
|
||||
padding-left: 30px;
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
.pool-name {
|
||||
display: inline-block;
|
||||
vertical-align: text-top;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.height {
|
||||
width: 10%;
|
||||
@media (max-width: 1100px) {
|
||||
width: 10%;
|
||||
}
|
||||
}
|
||||
.height.widget {
|
||||
width: 20%;
|
||||
@media (max-width: 576px) {
|
||||
width: 10%;
|
||||
}
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
@media (max-width: 900px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mined {
|
||||
width: 13%;
|
||||
@media (max-width: 576px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.txs {
|
||||
padding-right: 40px;
|
||||
@media (max-width: 1100px) {
|
||||
padding-right: 10px;
|
||||
}
|
||||
@media (max-width: 875px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.txs.widget {
|
||||
padding-right: 0;
|
||||
@media (max-width: 650px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fees {
|
||||
@media (max-width: 650px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.fees.widget {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.reward {
|
||||
@media (max-width: 576px) {
|
||||
width: 7%;
|
||||
padding-right: 30px;
|
||||
}
|
||||
}
|
||||
.reward.widget {
|
||||
width: 20%;
|
||||
@media (max-width: 576px) {
|
||||
width: 30%;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.size {
|
||||
width: 12%;
|
||||
@media (max-width: 1000px) {
|
||||
width: 15%;
|
||||
}
|
||||
@media (max-width: 650px) {
|
||||
width: 20%;
|
||||
}
|
||||
@media (max-width: 450px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { BehaviorSubject, combineLatest, Observable, timer } from 'rxjs';
|
||||
import { delayWhen, map, retryWhen, scan, skip, switchMap, tap } from 'rxjs/operators';
|
||||
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-blocks-list',
|
||||
templateUrl: './blocks-list.component.html',
|
||||
styleUrls: ['./blocks-list.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BlocksList implements OnInit {
|
||||
@Input() widget: boolean = false;
|
||||
|
||||
blocks$: Observable<any[]> = undefined;
|
||||
|
||||
isLoading = true;
|
||||
fromBlockHeight = undefined;
|
||||
paginationMaxSize: number;
|
||||
page = 1;
|
||||
lastPage = 1;
|
||||
blocksCount: number;
|
||||
fromHeightSubject: BehaviorSubject<number> = new BehaviorSubject(this.fromBlockHeight);
|
||||
skeletonLines: number[] = [];
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private websocketService: WebsocketService,
|
||||
public stateService: StateService,
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.websocketService.want(['blocks']);
|
||||
|
||||
this.skeletonLines = this.widget === true ? [...Array(5).keys()] : [...Array(15).keys()];
|
||||
this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
|
||||
|
||||
this.blocks$ = combineLatest([
|
||||
this.fromHeightSubject.pipe(
|
||||
switchMap((fromBlockHeight) => {
|
||||
this.isLoading = true;
|
||||
return this.apiService.getBlocks$(this.page === 1 ? undefined : fromBlockHeight)
|
||||
.pipe(
|
||||
tap(blocks => {
|
||||
if (this.blocksCount === undefined) {
|
||||
this.blocksCount = blocks[0].height;
|
||||
}
|
||||
this.isLoading = false;
|
||||
}),
|
||||
map(blocks => {
|
||||
for (const block of blocks) {
|
||||
// @ts-ignore: Need to add an extra field for the template
|
||||
block.extras.pool.logo = `./resources/mining-pools/` +
|
||||
block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg';
|
||||
}
|
||||
if (this.widget) {
|
||||
return blocks.slice(0, 5);
|
||||
}
|
||||
return blocks;
|
||||
}),
|
||||
retryWhen(errors => errors.pipe(delayWhen(() => timer(1000))))
|
||||
)
|
||||
})
|
||||
),
|
||||
this.stateService.blocks$
|
||||
.pipe(
|
||||
skip(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT - 1),
|
||||
),
|
||||
])
|
||||
.pipe(
|
||||
scan((acc, blocks) => {
|
||||
if (this.page > 1 || acc.length === 0 || (this.page === 1 && this.lastPage !== 1)) {
|
||||
this.lastPage = this.page;
|
||||
return blocks[0];
|
||||
}
|
||||
this.blocksCount = Math.max(this.blocksCount, blocks[1][0].height);
|
||||
// @ts-ignore: Need to add an extra field for the template
|
||||
blocks[1][0].extras.pool.logo = `./resources/mining-pools/` +
|
||||
blocks[1][0].extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg';
|
||||
acc.unshift(blocks[1][0]);
|
||||
acc = acc.slice(0, this.widget ? 5 : 15);
|
||||
return acc;
|
||||
}, [])
|
||||
);
|
||||
}
|
||||
|
||||
pageChange(page: number) {
|
||||
this.fromHeightSubject.next(this.blocksCount - (page - 1) * 15);
|
||||
}
|
||||
|
||||
trackByBlock(index: number, block: BlockExtended) {
|
||||
return block.height;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<div>
|
||||
<table class="table latest-transactions" style="min-height: 295px">
|
||||
<div style="min-height: 295px">
|
||||
<table class="table latest-transactions">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="d-none d-md-block" i18n="block.height">Height</th>
|
||||
@@ -17,7 +17,7 @@
|
||||
</td>
|
||||
<td class="text-right">{{ diffChange.difficultyShorten }}</td>
|
||||
<td class="text-right" [style]="diffChange.change >= 0 ? 'color: #42B747' : 'color: #B74242'">
|
||||
{{ diffChange.change >= 0 ? '+' : '' }}{{ formatNumber(diffChange.change, locale, '1.2-2') }}%
|
||||
{{ diffChange.change >= 0 ? '+' : '' }}{{ diffChange.change | amountShortener }}%
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -43,7 +43,7 @@ export class DifficultyAdjustmentsTable implements OnInit {
|
||||
const change = (data.difficulty[i].difficulty / data.difficulty[i - 1].difficulty - 1) * 100;
|
||||
|
||||
tableData.push(Object.assign(data.difficulty[i], {
|
||||
change: change,
|
||||
change: Math.round(change * 100) / 100,
|
||||
difficultyShorten: formatNumber(
|
||||
data.difficulty[i].difficulty / selectedPowerOfTen.divider,
|
||||
this.locale, '1.2-2') + selectedPowerOfTen.unit
|
||||
|
||||
@@ -5,30 +5,32 @@
|
||||
<!-- Temporary stuff here - Will be moved to a component once we have more useful data to show -->
|
||||
<div class="col">
|
||||
<div class="main-title">Reward stats</div>
|
||||
<div class="card" style="height: 123px">
|
||||
<div class="card-body more-padding">
|
||||
<div class="fee-estimation-container" *ngIf="$rewardStats | async as rewardStats; else loadingReward">
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="">Miners Reward</h5>
|
||||
<div class="card-text">
|
||||
<app-amount [satoshis]="rewardStats.totalReward" digitsInfo="1.2-2" [noFiat]="true"></app-amount>
|
||||
<div class="symbol">in the last 8 blocks</div>
|
||||
<div class="card-wrapper">
|
||||
<div class="card" style="height: 123px">
|
||||
<div class="card-body more-padding">
|
||||
<div class="reward-container" *ngIf="$rewardStats | async as rewardStats; else loadingReward">
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="mining.rewards">Miners Reward</h5>
|
||||
<div class="card-text">
|
||||
<app-amount [satoshis]="rewardStats.totalReward" digitsInfo="1.2-2" [noFiat]="true"></app-amount>
|
||||
<div class="symbol">in the last 8 blocks</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="">Reward Per Tx</h5>
|
||||
<div class="card-text">
|
||||
{{ rewardStats.rewardPerTx | amountShortener }}
|
||||
<span class="symbol">sats/tx</span>
|
||||
<div class="symbol">in the last 8 blocks</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="mining.rewards-per-tx">Reward Per Tx</h5>
|
||||
<div class="card-text">
|
||||
{{ rewardStats.rewardPerTx | amountShortener }}
|
||||
<span class="symbol">sats/tx</span>
|
||||
<div class="symbol">in the last 8 blocks</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="">Average Fee</h5>
|
||||
<div class="card-text">
|
||||
{{ rewardStats.feePerTx | amountShortener}}
|
||||
<span class="symbol">sats/tx</span>
|
||||
<div class="symbol">in the last 8 blocks</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="mining.average-fee">Average Fee</h5>
|
||||
<div class="card-text">
|
||||
{{ rewardStats.feePerTx | amountShortener}}
|
||||
<span class="symbol">sats/tx</span>
|
||||
<div class="symbol">in the last 8 blocks</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -36,24 +38,24 @@
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #loadingReward>
|
||||
<div class="fee-estimation-container">
|
||||
<div class="reward-container">
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="">Miners Reward</h5>
|
||||
<div class="card-text">
|
||||
<h5 class="card-title" i18n="mining.rewards">Miners Reward</h5>
|
||||
<div class="card-text skeleton">
|
||||
<div class="skeleton-loader"></div>
|
||||
<div class="skeleton-loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="">Reward Per Tx</h5>
|
||||
<div class="card-text">
|
||||
<h5 class="card-title" i18n="mining.rewards-per-tx">Reward Per Tx</h5>
|
||||
<div class="card-text skeleton">
|
||||
<div class="skeleton-loader"></div>
|
||||
<div class="skeleton-loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="">Average Fee</h5>
|
||||
<div class="card-text">
|
||||
<h5 class="card-title" i18n="mining.average-fee">Average Fee</h5>
|
||||
<div class="card-text skeleton">
|
||||
<div class="skeleton-loader"></div>
|
||||
<div class="skeleton-loader"></div>
|
||||
</div>
|
||||
@@ -95,7 +97,7 @@
|
||||
</div>
|
||||
|
||||
<!-- pool dominance -->
|
||||
<div class="col">
|
||||
<!-- <div class="col">
|
||||
<div class="card" style="height: 385px">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
@@ -106,6 +108,20 @@
|
||||
more »</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- Latest blocks -->
|
||||
<div class="col">
|
||||
<div class="card" style="height: 385px">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
Latest blocks
|
||||
</h5>
|
||||
<app-blocks-list [widget]=true></app-blocks-list>
|
||||
<div><a [routerLink]="['/mining/blocks' | relativeUrl]" i18n="dashboard.view-more">View
|
||||
more »</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
@@ -115,11 +131,11 @@
|
||||
Adjustments
|
||||
</h5>
|
||||
<app-difficulty-adjustments-table></app-difficulty-adjustments-table>
|
||||
<div class="mt-1"><a [routerLink]="['/mining/hashrate' | relativeUrl]" i18n="dashboard.view-more">View more
|
||||
<div><a [routerLink]="['/mining/hashrate' | relativeUrl]" i18n="dashboard.view-more">View more
|
||||
»</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,6 +28,9 @@
|
||||
.card-body.pool-ranking {
|
||||
padding: 1.25rem 0.25rem 0.75rem 0.25rem;
|
||||
}
|
||||
.card-text {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
#blockchain-container {
|
||||
position: relative;
|
||||
@@ -56,50 +59,57 @@
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.fee-estimation-container {
|
||||
.reward-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@media (min-width: 376px) {
|
||||
flex-direction: row;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
height: 76px;
|
||||
.shared-block {
|
||||
color: #ffffff66;
|
||||
font-size: 12px;
|
||||
}
|
||||
.item {
|
||||
max-width: 150px;
|
||||
margin: 0;
|
||||
width: -webkit-fill-available;
|
||||
@media (min-width: 376px) {
|
||||
margin: 0 auto 0px;
|
||||
}
|
||||
&:first-child{
|
||||
display: table-cell;
|
||||
padding: 0 5px;
|
||||
width: 100%;
|
||||
&:nth-child(1) {
|
||||
display: none;
|
||||
@media (min-width: 485px) {
|
||||
display: block;
|
||||
display: table-cell;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
display: block;
|
||||
display: table-cell;
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.card-text span {
|
||||
color: #ffffff66;
|
||||
font-size: 12px;
|
||||
top: 0px;
|
||||
}
|
||||
.fee-text{
|
||||
border-bottom: 1px solid #ffffff1c;
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
line-height: 1.45;
|
||||
padding: 0px 2px;
|
||||
}
|
||||
.fiat {
|
||||
display: block;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
}
|
||||
.card-text {
|
||||
font-size: 22px;
|
||||
margin-top: -9px;
|
||||
position: relative;
|
||||
}
|
||||
.card-text.skeleton {
|
||||
margin-top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.more-padding {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.card-wrapper {
|
||||
.card {
|
||||
height: auto !important;
|
||||
}
|
||||
.card-body {
|
||||
display: flex;
|
||||
flex: inherit;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
padding: 22px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ export class MiningDashboardComponent implements OnInit {
|
||||
|
||||
return {
|
||||
'totalReward': totalReward,
|
||||
'rewardPerTx': totalReward / totalTx,
|
||||
'feePerTx': totalFee / totalTx,
|
||||
'rewardPerTx': Math.round(totalReward / totalTx),
|
||||
'feePerTx': Math.round(totalFee / totalTx),
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -21,7 +21,10 @@
|
||||
<table class="table table-borderless smaller-text table-sm" id="table-tx-vin">
|
||||
<tbody>
|
||||
<ng-template ngFor let-vin [ngForOf]="tx['@vinLimit'] ? ((tx.vin.length>12)?tx.vin.slice(0, 10): tx.vin.slice(0, 12)) : tx.vin" [ngForTrackBy]="trackByIndexFn">
|
||||
<tr [ngClass]="assetsMinimal && vin.prevout && assetsMinimal[vin.prevout.asset] && !vin.is_coinbase && vin.prevout.scriptpubkey_address && tx._unblinded ? 'assetBox' : ''">
|
||||
<tr [ngClass]="{
|
||||
'assetBox': assetsMinimal && vin.prevout && assetsMinimal[vin.prevout.asset] && !vin.is_coinbase && vin.prevout.scriptpubkey_address && tx._unblinded,
|
||||
'highlight': vin.prevout?.scriptpubkey_address === this.address && this.address !== ''
|
||||
}">
|
||||
<td class="arrow-td">
|
||||
<ng-template [ngIf]="vin.prevout === null && !vin.is_pegin" [ngIfElse]="hasPrevout">
|
||||
<span class="grey">
|
||||
@@ -143,7 +146,10 @@
|
||||
<table class="table table-borderless smaller-text table-sm" id="table-tx-vout">
|
||||
<tbody>
|
||||
<ng-template ngFor let-vout let-vindex="index" [ngForOf]="tx['@voutLimit'] && !outputIndex ? ((tx.vout.length > 12) ? tx.vout.slice(0, 10) : tx.vout.slice(0, 12)) : tx.vout" [ngForTrackBy]="trackByIndexFn">
|
||||
<tr [ngClass]="assetsMinimal && assetsMinimal[vout.asset] && vout.scriptpubkey_address && tx.vin && !tx.vin[0].is_coinbase && tx._unblinded || outputIndex === vindex ? 'assetBox' : ''">
|
||||
<tr [ngClass]="{
|
||||
'assetBox': assetsMinimal && assetsMinimal[vout.asset] && vout.scriptpubkey_address && tx.vin && !tx.vin[0].is_coinbase && tx._unblinded || outputIndex === vindex,
|
||||
'highlight': vout.scriptpubkey_address === this.address && this.address !== ''
|
||||
}">
|
||||
<td>
|
||||
<a *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
|
||||
<span class="d-block d-lg-none">{{ vout.scriptpubkey_address | shortenString : 16 }}</span>
|
||||
@@ -242,13 +248,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="summary">
|
||||
<div class="float-left mt-2-5" *ngIf="!transactionPage && !tx.vin[0].is_coinbase">
|
||||
{{ tx.fee / (tx.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="d-none d-sm-inline-block"> – {{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [value]="tx.fee"></app-fiat></span></span>
|
||||
</div>
|
||||
|
||||
<div class="float-right">
|
||||
<span *ngIf="showConfirmations && latestBlock$ | async as latestBlock">
|
||||
<ng-container *ngIf="showConfirmations && latestBlock$ | async as latestBlock">
|
||||
<button *ngIf="tx.status.confirmed; else unconfirmedButton" type="button" class="btn btn-sm btn-success mt-2">
|
||||
<ng-container *ngTemplateOutlet="latestBlock.height - tx.status.block_height + 1 == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: latestBlock.height - tx.status.block_height + 1}"></ng-container>
|
||||
<ng-template #confirmationSingular let-i i18n="shared.confirmation-count.singular|Transaction singular confirmation count">{{ i }} confirmation</ng-template>
|
||||
@@ -257,14 +263,18 @@
|
||||
<ng-template #unconfirmedButton>
|
||||
<button type="button" class="btn btn-sm btn-danger mt-2" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
||||
</ng-template>
|
||||
|
||||
</span>
|
||||
<button type="button" class="btn btn-sm btn-primary mt-2" (click)="switchCurrency()">
|
||||
</ng-container>
|
||||
<button *ngIf="address === ''; else viewingAddress" type="button" class="btn btn-sm btn-primary mt-2 ml-2" (click)="switchCurrency()">
|
||||
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && haveBlindedOutputValues(tx)" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
|
||||
<ng-template #defaultAmount>
|
||||
<app-amount [satoshis]="getTotalTxOutput(tx)"></app-amount>
|
||||
</ng-template>
|
||||
</button>
|
||||
<ng-template #viewingAddress>
|
||||
<button type="button" class="btn btn-sm mt-2 ml-2" (click)="switchCurrency()" [ngClass]="{'btn-success': tx['addressValue'] >= 0, 'btn-danger': tx['addressValue'] < 0}">
|
||||
<app-amount [satoshis]="tx['addressValue']" [addPlus]="true"></app-amount>
|
||||
</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
|
||||
.arrow-td {
|
||||
width: 20px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.green, .grey, .red {
|
||||
font-size: 16px;
|
||||
top: -2px;
|
||||
top: 1px;
|
||||
position: relative;
|
||||
@media( min-width: 576px){
|
||||
font-size: 19px;
|
||||
@@ -119,3 +121,11 @@
|
||||
h2 {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: #181b2d;
|
||||
}
|
||||
|
||||
.summary {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Observable, forkJoin, ReplaySubject, BehaviorSubject, merge, of, Subject, Subscription } from 'rxjs';
|
||||
import { Outspend, Transaction } from '../../interfaces/electrs.interface';
|
||||
import { Observable, forkJoin, ReplaySubject, BehaviorSubject, merge, Subscription } from 'rxjs';
|
||||
import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.interface';
|
||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { AssetsService } from 'src/app/services/assets.service';
|
||||
import { map, share, switchMap, tap } from 'rxjs/operators';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
|
||||
|
||||
@Component({
|
||||
@@ -23,6 +23,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
@Input() transactionPage = false;
|
||||
@Input() errorUnblinded = false;
|
||||
@Input() outputIndex: number;
|
||||
@Input() address: string = '';
|
||||
|
||||
@Output() loadMore = new EventEmitter();
|
||||
|
||||
@@ -98,6 +99,21 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
if (this.outspends[i]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.address) {
|
||||
const addressIn = tx.vout
|
||||
.filter((v: Vout) => v.scriptpubkey_address === this.address)
|
||||
.map((v: Vout) => v.value || 0)
|
||||
.reduce((a: number, b: number) => a + b, 0);
|
||||
|
||||
const addressOut = tx.vin
|
||||
.filter((v: Vin) => v.prevout && v.prevout.scriptpubkey_address === this.address)
|
||||
.map((v: Vin) => v.prevout.value || 0)
|
||||
.reduce((a: number, b: number) => a + b, 0);
|
||||
|
||||
tx['addressValue'] = addressIn || -addressOut;
|
||||
}
|
||||
|
||||
observableObject[i] = this.electrsApiService.getOutspends$(tx.txid);
|
||||
});
|
||||
this.refreshOutspends$.next(observableObject);
|
||||
@@ -119,7 +135,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
getTotalTxOutput(tx: Transaction) {
|
||||
return tx.vout.map((v: any) => v.value || 0).reduce((a: number, b: number) => a + b);
|
||||
return tx.vout.map((v: Vout) => v.value || 0).reduce((a: number, b: number) => a + b);
|
||||
}
|
||||
|
||||
switchCurrency() {
|
||||
|
||||
@@ -72,7 +72,7 @@ export interface Vout {
|
||||
scriptpubkey: string;
|
||||
scriptpubkey_asm: string;
|
||||
scriptpubkey_type: string;
|
||||
scriptpubkey_address: string;
|
||||
scriptpubkey_address?: string;
|
||||
value: number;
|
||||
// Elements
|
||||
valuecommitment?: number;
|
||||
|
||||
@@ -151,6 +151,13 @@ export class ApiService {
|
||||
);
|
||||
}
|
||||
|
||||
getBlocks$(from: number): Observable<BlockExtended[]> {
|
||||
return this.httpClient.get<BlockExtended[]>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/blocks-extras` +
|
||||
(from !== undefined ? `/${from}` : ``)
|
||||
);
|
||||
}
|
||||
|
||||
getHistoricalDifficulty$(interval: string | undefined): Observable<any> {
|
||||
return this.httpClient.get<any[]>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty` +
|
||||
|
||||
@@ -1,22 +1,39 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
// https://medium.com/@thunderroid/angular-short-number-suffix-pipe-1k-2m-3b-dded4af82fb4
|
||||
|
||||
@Pipe({
|
||||
name: 'amountShortener'
|
||||
})
|
||||
export class AmountShortenerPipe implements PipeTransform {
|
||||
transform(num: number, ...args: number[]): unknown {
|
||||
const digits = args[0] || 1;
|
||||
const lookup = [
|
||||
{ value: 1, symbol: '' },
|
||||
{ value: 1e3, symbol: 'k' },
|
||||
{ value: 1e6, symbol: 'M' },
|
||||
{ value: 1e9, symbol: 'G' },
|
||||
{ value: 1e12, symbol: 'T' },
|
||||
{ value: 1e15, symbol: 'P' },
|
||||
{ value: 1e18, symbol: 'E' }
|
||||
transform(number: number, args?: any): any {
|
||||
if (isNaN(number)) return null; // will only work value is a number
|
||||
if (number === null) return null;
|
||||
if (number === 0) return null;
|
||||
let abs = Math.abs(number);
|
||||
const rounder = Math.pow(10, 1);
|
||||
const isNegative = number < 0; // will also work for Negetive numbers
|
||||
let key = '';
|
||||
|
||||
const powers = [
|
||||
{ key: 'E', value: 10e18 },
|
||||
{ key: 'P', value: 10e15 },
|
||||
{ key: 'T', value: 10e12 },
|
||||
{ key: 'B', value: 10e9 },
|
||||
{ key: 'M', value: 10e6 },
|
||||
{ key: 'K', value: 1000 }
|
||||
];
|
||||
const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
|
||||
var item = lookup.slice().reverse().find((item) => num >= item.value);
|
||||
return item ? (num / item.value).toFixed(digits).replace(rx, '$1') + item.symbol : '0';
|
||||
|
||||
for (let i = 0; i < powers.length; i++) {
|
||||
let reduced = abs / powers[i].value;
|
||||
reduced = Math.round(reduced * rounder) / rounder;
|
||||
if (reduced >= 1) {
|
||||
abs = reduced;
|
||||
key = powers[i].key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (isNegative ? '-' : '') + abs + key;
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
frontend/src/resources/profile/start9.png
Normal file
BIN
frontend/src/resources/profile/start9.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Reference in New Issue
Block a user