Merge branch 'master' into simon/remove-bisq

This commit is contained in:
softsimon
2024-04-01 18:52:56 +09:00
committed by GitHub
74 changed files with 8049 additions and 7560 deletions

View File

@@ -156,7 +156,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10))}</b><br>`;
if (ticks[0].data[1] > 10_000_000) {
tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1] / 100_000_000, this.locale, '1.0-0')} BTC<br>`;
tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1] / 100_000_000, this.locale, '1.0-8')} BTC<br>`;
} else {
tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1], this.locale, '1.0-0')} sats<br>`;
}

View File

@@ -1,5 +1,7 @@
<div *ngIf="featuredAssets$ | async as featured; else loading" class="featuredBox">
<div *ngIf="featured.length === 0" class="text-center">
<div i18n="liquid.no-featured.assets" class="symbol">No featured assets</div>
</div>
<div class="card" *ngFor="let group of featured">
<ng-template [ngIf]="group.assets" [ngIfElse]="singleAsset">
<a [routerLink]="['/assets/group' | relativeUrl, group.id]">

View File

@@ -47,3 +47,9 @@
.ticker {
color: grey;
}
.symbol {
color: grey;
font-size: 1.5rem;
font-style: italic;
}

View File

@@ -1,6 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { ApiService } from '../../../services/api.service';
import { StateService } from '../../../services/state.service';
@Component({
selector: 'app-assets-featured',
@@ -12,10 +13,11 @@ export class AssetsFeaturedComponent implements OnInit {
constructor(
private apiService: ApiService,
private stateService: StateService,
) { }
ngOnInit(): void {
this.featuredAssets$ = this.apiService.listFeaturedAssets$();
this.featuredAssets$ = this.apiService.listFeaturedAssets$(this.stateService.network);
}
}

View File

@@ -1,5 +1,5 @@
<div class="grid-align" [style.gridTemplateColumns]="'repeat(auto-fit, ' + resolution + 'px)'">
<div class="graph-alignment" [class.grid-align]="!autofit" [style.gridTemplateColumns]="'repeat(auto-fit, ' + resolution + 'px)'">
<div class="block-overview-graph">
<canvas *browserOnly class="block-overview-canvas" [class.clickable]="!!hoverTx" #blockCanvas></canvas>
<div class="loader-wrapper" [class.hidden]="(!isLoading || disableSpinner) && !unavailable">

View File

@@ -22,9 +22,12 @@
}
}
.grid-align {
.graph-alignment {
position: relative;
width: 100%;
}
.grid-align {
display: grid;
grid-template-columns: repeat(auto-fit, 75px);
justify-content: center;

View File

@@ -32,6 +32,7 @@ const unmatchedAuditColors = {
export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, OnChanges {
@Input() isLoading: boolean;
@Input() resolution: number;
@Input() autofit: boolean = false;
@Input() blockLimit: number;
@Input() orientation = 'left';
@Input() flip = true;
@@ -206,6 +207,10 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined, acc: boolean | undefined }[], direction: string = 'left', resetLayout: boolean = false): void {
if (this.scene) {
add = add.filter(tx => !this.scene.txs[tx.txid]);
remove = remove.filter(txid => this.scene.txs[txid]);
change = change.filter(tx => this.scene.txs[tx.txid]);
this.scene.update(add, remove, change, direction, resetLayout);
this.start();
this.updateSearchHighlight();

View File

@@ -61,10 +61,10 @@
</td>
</tr>
{{ activeFilters.rbf }}
<tr *ngIf="(!auditEnabled && tx && tx.status === 'accelerated') || filters.length">
<tr *ngIf="(!auditEnabled && tx && (tx.status === 'accelerated' || tx.acc || acceleration)) || filters.length">
<td colspan="2">
<div class="tags mt-2" [class.any-mode]="filterMode === 'or'">
<span *ngIf="!auditEnabled && tx && tx.status === 'accelerated'" class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span>
<span *ngIf="!auditEnabled && tx && (tx.status === 'accelerated' || tx.acc || acceleration)" class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span>
<ng-container *ngFor="let filter of filters;">
<span class="btn badge filter-tag" [class.matching]="activeFilters[filter.key]">{{ filter.label }}</span>
</ng-container>

View File

@@ -9,8 +9,8 @@
justify-content: space-between;
padding: 10px 15px;
text-align: left;
min-width: 320px;
max-width: 320px;
min-width: 340px;
max-width: 340px;
pointer-events: none;
z-index: 11;

View File

@@ -61,8 +61,8 @@ export class BlockOverviewTooltipComponent implements OnChanges {
this.vsize = this.tx.vsize || 1;
this.feeRate = this.fee / this.vsize;
this.effectiveRate = this.tx.rate;
this.acceleration = this.tx.acc;
const txFlags = BigInt(this.tx.flags) || 0n;
this.acceleration = this.tx.acc || (txFlags & TransactionFlags.acceleration);
this.hasEffectiveRate = Math.abs((this.fee / this.vsize) - this.effectiveRate) > 0.05
|| (txFlags && (txFlags & (TransactionFlags.cpfp_child | TransactionFlags.cpfp_parent)) > 0n);
this.filters = this.tx.flags ? toFilters(txFlags).filter(f => f.tooltip) : [];

View File

@@ -70,8 +70,9 @@
<div class="col-sm chart-container">
<app-block-overview-graph
#blockGraph
[autofit]="true"
[isLoading]="false"
[resolution]="80"
[resolution]="86"
[blockLimit]="stateService.blockVSize"
[orientation]="'top'"
[flip]="false"

View File

@@ -40,12 +40,14 @@
<app-fee-rate unitClass=""></app-fee-rate>
</div>
</ng-template>
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-total-fees'" *ngIf="showMiningInfo"
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-total-fees'" *ngIf="showMiningInfo$ | async; else noMiningInfo"
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-' + offset + '-index-' + i + 'block-size'" *ngIf="!showMiningInfo"
<ng-template #noMiningInfo>
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + 'block-size'"
class="block-size" [innerHTML]="'&lrm;' + (block.size | bytes: 2)"></div>
</ng-template>
<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>

View File

@@ -1,5 +1,5 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { StateService } from '../../services/state.service';
import { specialBlocks } from '../../app.constants';
import { BlockExtended } from '../../interfaces/node-api.interface';
@@ -45,6 +45,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
markBlockSubscription: Subscription;
txConfirmedSubscription: Subscription;
loadingBlocks$: Observable<boolean>;
showMiningInfo$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
blockStyles = [];
emptyBlockStyles = [];
interval: any;
@@ -54,7 +55,6 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
arrowLeftPx = 30;
blocksFilled = false;
arrowTransition = '1s';
showMiningInfo = false;
timeLtrSubscription: Subscription;
timeLtr: boolean;
@@ -79,8 +79,11 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
}
enabledMiningInfoIfNeeded(url) {
this.showMiningInfo = url.includes('/mining') || url.includes('/acceleration');
this.cd.markForCheck(); // Need to update the view asap
const urlParts = url.split('/');
const onDashboard = ['','testnet','signet','mining','acceleration'].includes(urlParts[urlParts.length - 1]);
if (onDashboard) { // Only update showMiningInfo if we are on the main, mining or acceleration dashboards
this.stateService.showMiningInfo$.next(url.includes('/mining') || url.includes('/acceleration'));
}
}
ngOnInit() {
@@ -89,6 +92,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
if (['', 'testnet', 'signet'].includes(this.stateService.network)) {
this.enabledMiningInfoIfNeeded(this.location.path());
this.location.onUrlChange((url) => this.enabledMiningInfoIfNeeded(url));
this.showMiningInfo$ = this.stateService.showMiningInfo$;
}
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {

View File

@@ -56,7 +56,7 @@
</div>
</ng-template>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,7 +1,15 @@
<div *ngIf="showTitle" class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div>
<div *ngIf="showTitle && mode === 'difficulty'" class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div>
<div *ngIf="showTitle && mode === 'halving'" class="main-title" i18n="dashboard.halving-countdown">Halving Countdown</div>
<div class="card-wrapper">
<div class="card">
<div class="card-body more-padding">
<div class="widget-toggler">
<a href="" (click)="setMode('difficulty')" class="toggler-option"
[ngClass]="{'inactive': mode === 'difficulty'}"><small i18n="statistics.average-small">difficulty</small></a>
<span style="color: #ffffff66; font-size: 8px"> | </span>
<a href="" (click)="setMode('halving')" class="toggler-option"
[ngClass]="{'inactive': mode === 'halving'}"><small i18n="statistics.median-small">halving</small></a>
</div>
<div *ngIf="mode === 'difficulty'; else halving" 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 #epochSvg class="epoch-blocks" height="22px" width="100%" viewBox="0 0 224 9" shape-rendering="crispEdges" preserveAspectRatio="none">
@@ -76,6 +84,52 @@
</div>
</div>
<ng-template #halving>
<div class="card-body more-padding">
<div class="difficulty-adjustment-container halving" *ngIf="(isLoadingWebSocket$ | async) === false && (difficultyEpoch$ | async) as epochData; else loadingDifficulty">
<div class="halving-progress">
<div class="background"></div>
<div class="remaining" [style]="{ left: ((210000 - epochData.blocksUntilHalving) / 2100).toFixed(2) + '%' }"></div>
<div class="label">
{{ ((210000 - epochData.blocksUntilHalving) / 2100).toFixed(2) }}%
</div>
</div>
<div class="difficulty-stats">
<div class="item">
<div class="card-text bigger">
<app-btc [satoshis]="nextSubsidy"></app-btc>
</div>
<div class="symbol">
<span i18n="difficulty-box.new-subsidy">New subsidy</span>
</div>
</div>
<div class="item">
<div class="card-text">
{{ epochData.blocksUntilHalving | number }}
</div>
<div class="symbol">
<span *ngIf="epochData.blocksUntilHalving > 1" i18n="shared.blocks-remaining">Blocks remaining</span>
<span *ngIf="epochData.blocksUntilHalving === 1" i18n="shared.block-remaining">Block remaining</span>
</div>
</div>
<div class="item">
<div class="card-text" i18n-ngbTooltip="mining.average-fee" placement="bottom">
<span>{{ epochData.timeUntilHalving | date }}</span>
</div>
<div class="symbol" *ngIf="epochData.blocksUntilHalving === 1; else approxTime">
<app-time kind="until" [time]="epochData.adjustedTimeAvg + now" [fastRender]="false" [fixedRender]="true" [precision]="1" minUnit="minute"></app-time>
</div>
<ng-template #approxTime>
<div class="symbol">
<app-time kind="until" [time]="epochData.timeUntilHalving" [fastRender]="false" [fixedRender]="true" [precision]="0" [numUnits]="2" [units]="['year', 'day', 'hour', 'minute']"></app-time>
</div>
</ng-template>
</div>
</div>
</div>
</div>
</ng-template>
<ng-template #loadingDifficulty>
<div class="epoch-progress">
<div class="skeleton-loader"></div>

View File

@@ -168,7 +168,7 @@
white-space: nowrap;
}
.epoch-progress {
.epoch-progress, .halving-progress {
width: 100%;
height: 22px;
margin-bottom: 12px;
@@ -212,4 +212,43 @@
}
.blocks-behind {
color: #D81B60;
}
.halving-progress {
position: relative;
.background, .remaining {
position: absolute;
top: 0;
bottom: 0;
height: 100%;
}
.background {
background: linear-gradient(to right, #105fb0, #9339f4);
left: 0;
right: 0;
}
.remaining {
background: #2d3348;
right: 0;
}
.label {
position: relative;
margin: auto;
}
}
.widget-toggler {
font-size: 12px;
position: absolute;
top: -20px;
right: 3px;
text-align: right;
}
.toggler-option {
text-decoration: none;
}
.inactive {
color: #ffffff66;
}

View File

@@ -51,6 +51,10 @@ export class DifficultyComponent implements OnInit {
isLoadingWebSocket$: Observable<boolean>;
difficultyEpoch$: Observable<EpochProgress>;
mode: 'difficulty' | 'halving' = 'difficulty';
userSelectedMode: boolean = false;
now: number = Date.now();
epochStart: number;
currentHeight: number;
currentIndex: number;
@@ -58,6 +62,7 @@ export class DifficultyComponent implements OnInit {
expectedIndex: number;
difference: number;
shapes: DiffShape[];
nextSubsidy: number;
tooltipPosition = { x: 0, y: 0 };
hoverSection: DiffShape | void;
@@ -101,6 +106,12 @@ export class DifficultyComponent implements OnInit {
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);
this.now = new Date().getTime();
this.nextSubsidy = getNextBlockSubsidy(maxHeight);
if (blocksUntilHalving < da.remainingBlocks && !this.userSelectedMode) {
this.mode = 'halving';
}
if (newEpochStart !== this.epochStart || newExpectedHeight !== this.expectedHeight || this.currentHeight !== this.stateService.latestBlockHeight) {
this.epochStart = newEpochStart;
@@ -194,6 +205,12 @@ export class DifficultyComponent implements OnInit {
return shapes;
}
setMode(mode: 'difficulty' | 'halving'): boolean {
this.mode = mode;
this.userSelectedMode = true;
return false;
}
@HostListener('pointerdown', ['$event'])
onPointerDown(event): void {
if (this.epochSvgElement?.nativeElement?.contains(event.target)) {
@@ -218,3 +235,16 @@ export class DifficultyComponent implements OnInit {
this.hoverSection = null;
}
}
function getNextBlockSubsidy(height: number): number {
const halvings = Math.floor(height / 210_000) + 1;
// Force block reward to zero when right shift is undefined.
if (halvings >= 64) {
return 0;
}
let subsidy = BigInt(50 * 100_000_000);
// Subsidy is cut in half every 210,000 blocks which will occur approximately every 4 years.
subsidy >>= BigInt(halvings);
return Number(subsidy);
}

View File

@@ -66,7 +66,7 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges, OnDestr
return;
}
const samples = [];
const txs = this.transactions.filter(tx => !tx.acc).map(tx => { return { vsize: tx.vsize, rate: tx.rate || (tx.fee / tx.vsize) }; }).sort((a, b) => { return b.rate - a.rate; });
const txs = this.transactions.map(tx => { return { vsize: tx.vsize, rate: tx.rate || (tx.fee / tx.vsize) }; }).sort((a, b) => { return b.rate - a.rate; });
const maxBlockVSize = this.stateService.env.BLOCK_WEIGHT_UNITS / 4;
const sampleInterval = maxBlockVSize / this.numSamples;
let cumVSize = 0;

View File

@@ -339,6 +339,9 @@ export class HashrateChartComponent implements OnInit {
const newMin = Math.floor(value.min / selectedPowerOfTen.divider / 10);
return newMin * selectedPowerOfTen.divider * 10;
},
max: (value) => {
return value.max;
},
type: 'value',
axisLabel: {
color: 'rgb(110, 112, 121)',
@@ -357,11 +360,18 @@ export class HashrateChartComponent implements OnInit {
},
},
{
min: (value) => {
return value.min * 0.9;
},
type: 'value',
position: 'right',
min: (_) => {
const firstYAxisMin = this.chartInstance.getModel().getComponent('yAxis', 0).axis.scale.getExtent()[0];
const selectedPowerOfTen: any = selectPowerOfTen(firstYAxisMin);
const newMin = Math.floor(firstYAxisMin / selectedPowerOfTen.divider / 10)
return 600 / 2 ** 32 * newMin * selectedPowerOfTen.divider * 10;
},
max: (_) => {
const firstYAxisMax = this.chartInstance.getModel().getComponent('yAxis', 0).axis.scale.getExtent()[1];
return 600 / 2 ** 32 * firstYAxisMax;
},
axisLabel: {
color: 'rgb(110, 112, 121)',
formatter: (val): string => {

View File

@@ -265,8 +265,8 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
type: 'value',
axisLabel: {
fontSize: 11,
formatter: (value) => {
return this.weightMode ? value * 4 : value;
formatter: (value): string => {
return this.weightMode ? (value * 4).toString() : value.toString();
}
},
splitLine: {

View File

@@ -20,10 +20,12 @@
-
<app-fee-rate [fee]="projectedBlock.feeRange[projectedBlock.feeRange.length - 1]" rounding="1.0-0" unitClass=""></app-fee-rate>
</div>
<div *ngIf="showMiningInfo" class="block-size">
<div *ngIf="showMiningInfo$ | async; else noMiningInfo" class="block-size">
<app-amount [attr.data-cy]="'mempool-block-' + i + '-total-fees'" [satoshis]="projectedBlock.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
</div>
<div *ngIf="!showMiningInfo" class="block-size" [innerHTML]="'&lrm;' + (projectedBlock.blockSize | bytes: 2)"></div>
<ng-template #noMiningInfo>
<div class="block-size" [innerHTML]="'&lrm;' + (projectedBlock.blockSize | bytes: 2)"></div>
</ng-template>
<div [attr.data-cy]="'mempool-block-' + i + '-transaction-count'" class="transaction-count">
<ng-container *ngTemplateOutlet="projectedBlock.nTx === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: projectedBlock.nTx | number}"></ng-container>
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>

View File

@@ -1,5 +1,5 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, Input, OnChanges, SimpleChanges, Output, EventEmitter } from '@angular/core';
import { Subscription, Observable, of, combineLatest } from 'rxjs';
import { Subscription, Observable, of, combineLatest, BehaviorSubject } from 'rxjs';
import { MempoolBlock } from '../../interfaces/websocket.interface';
import { StateService } from '../../services/state.service';
import { Router } from '@angular/router';
@@ -42,6 +42,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
mempoolBlocks$: Observable<MempoolBlock[]>;
difficultyAdjustments$: Observable<DifficultyAdjustment>;
loadingBlocks$: Observable<boolean>;
showMiningInfo$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
blocksSubscription: Subscription;
mempoolBlocksFull: MempoolBlock[] = [];
@@ -57,7 +58,6 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
network = '';
now = new Date().getTime();
timeOffset = 0;
showMiningInfo = false;
timeLtrSubscription: Subscription;
timeLtr: boolean;
animateEntry: boolean = false;
@@ -89,11 +89,6 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
private location: Location,
) { }
enabledMiningInfoIfNeeded(url) {
this.showMiningInfo = url.includes('/mining') || url.includes('/acceleration');
this.cd.markForCheck(); // Need to update the view asap
}
ngOnInit() {
this.chainTip = this.stateService.latestBlockHeight;
@@ -102,8 +97,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
this.widthChange.emit(this.mempoolWidth);
if (['', 'testnet', 'signet'].includes(this.stateService.network)) {
this.enabledMiningInfoIfNeeded(this.location.path());
this.location.onUrlChange((url) => this.enabledMiningInfoIfNeeded(url));
this.showMiningInfo$ = this.stateService.showMiningInfo$;
}
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {

View File

@@ -411,7 +411,6 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
padding: [20, 0, 0, 0],
},
type: 'time',
boundaryGap: false,
axisLine: { onZero: true },
axisLabel: {
margin: 20,

View File

@@ -6,7 +6,7 @@
<span class="menu-click text-nowrap ellipsis">
<strong>
<span *ngIf="user.username.includes('@'); else usernamenospace">{{ user.username }}</span>
<ng-template #usernamenospace>@{{ user.username }}</ng-template>
<ng-template #usernamenospace>&#64;{{ user.username }}</ng-template>
</strong>
</span>
<span class="badge mr-1 badge-og" *ngIf="user.ogRank">

View File

@@ -32,6 +32,7 @@
}
.chart {
margin-top: 10px;
margin-bottom: 20px;
@media (max-width: 768px) {
margin-bottom: 10px;

View File

@@ -65,7 +65,9 @@ export class PoolComponent implements OnInit {
.pipe(
switchMap((data) => {
this.isLoading = false;
this.prepareChartOptions(data.map(val => [val.timestamp * 1000, val.avgHashrate]));
const hashrate = data.map(val => [val.timestamp * 1000, val.avgHashrate]);
const share = data.map(val => [val.timestamp * 1000, val.share * 100]);
this.prepareChartOptions(hashrate, share);
return [slug];
}),
catchError(() => {
@@ -130,9 +132,9 @@ export class PoolComponent implements OnInit {
);
}
prepareChartOptions(data) {
prepareChartOptions(hashrate, share) {
let title: object;
if (data.length <= 1) {
if (hashrate.length <= 1) {
title = {
textStyle: {
color: 'grey',
@@ -177,26 +179,57 @@ export class PoolComponent implements OnInit {
},
borderColor: '#000',
formatter: function (ticks: any[]) {
let hashratePowerOfTen: any = selectPowerOfTen(1);
let hashrate = ticks[0].data[1];
hashratePowerOfTen = selectPowerOfTen(ticks[0].data[1], 10);
hashrate = ticks[0].data[1] / hashratePowerOfTen.divider;
let hashrateString = '';
let dominanceString = '';
for (const tick of ticks) {
if (tick.seriesIndex === 0) {
let hashratePowerOfTen = selectPowerOfTen(tick.data[1], 10);
let hashrateData = tick.data[1] / hashratePowerOfTen.divider;
hashrateString = `${tick.marker} ${tick.seriesName}: ${formatNumber(hashrateData, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s<br>`;
} else if (tick.seriesIndex === 1) {
dominanceString = `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.0-2')}%`;
}
}
return `
<b style="color: white; margin-left: 18px">${ticks[0].axisValueLabel}</b><br>
<span>${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s</span><br>
<span>${hashrateString}</span>
<span>${dominanceString}</span>
`;
}.bind(this)
},
xAxis: data.length <= 1 ? undefined : {
xAxis: hashrate.length <= 1 ? undefined : {
type: 'time',
splitNumber: (this.isMobile()) ? 5 : 10,
axisLabel: {
hideOverlap: true,
}
},
yAxis: data.length <= 1 ? undefined : [
legend: {
data: [
{
name: $localize`:mining.hashrate:Hashrate`,
inactiveColor: 'rgb(110, 112, 121)',
textStyle: {
color: 'white',
},
icon: 'roundRect',
itemStyle: {
color: '#FFB300',
},
},
{
name: $localize`:mining.pool-dominance:Pool Dominance`,
inactiveColor: 'rgb(110, 112, 121)',
textStyle: {
color: 'white',
},
icon: 'roundRect',
},
],
},
yAxis: hashrate.length <= 1 ? undefined : [
{
min: (value) => {
return value.min * 0.9;
@@ -214,21 +247,45 @@ export class PoolComponent implements OnInit {
show: false,
}
},
],
series: data.length <= 1 ? undefined : [
{
zlevel: 0,
name: 'Hashrate',
type: 'value',
axisLabel: {
color: 'rgb(110, 112, 121)',
formatter: (val) => {
return `${val}%`
}
},
splitLine: {
show: false,
}
}
],
series: hashrate.length <= 1 ? undefined : [
{
zlevel: 1,
name: $localize`:mining.hashrate:Hashrate`,
showSymbol: false,
symbol: 'none',
data: data,
data: hashrate,
type: 'line',
lineStyle: {
width: 2,
},
},
{
zlevel: 0,
name: $localize`:mining.pool-dominance:Pool Dominance`,
showSymbol: false,
symbol: 'none',
data: share,
type: 'line',
yAxisIndex: 1,
lineStyle: {
width: 2,
},
}
],
dataZoom: data.length <= 1 ? undefined : [{
dataZoom: hashrate.length <= 1 ? undefined : [{
type: 'inside',
realtime: true,
zoomLock: true,

View File

@@ -24,7 +24,7 @@ export class StartComponent implements OnInit, AfterViewChecked, OnDestroy {
timeLtrSubscription: Subscription;
timeLtr: boolean = this.stateService.timeLtr.value;
chainTipSubscription: Subscription;
chainTip: number = 100;
chainTip: number = -1;
tipIsSet: boolean = false;
lastMark: MarkBlockState;
markBlockSubscription: Subscription;

View File

@@ -326,7 +326,7 @@
<br>
<p>If you have any questions about this Policy, would like to speak with us about the use of our Marks in ways not described in the Policy, or see any abuse of our Marks, please email us at &lt;legal@mempool.space&gt;</p>
<p>If you have any questions about this Policy, would like to speak with us about the use of our Marks in ways not described in the Policy, or see any abuse of our Marks, please email us at &lt;legal&#64;mempool.space&gt;</p>
</ol>

View File

@@ -70,12 +70,11 @@
<app-tx-features [tx]="tx"></app-tx-features>
</td>
</tr>
<tr *ngIf="network === ''">
<tr *ngIf="network === '' && auditStatus">
<td class="td-width" i18n="transaction.audit">Audit</td>
<td *ngIf="pool" class="wrap-cell">
<ng-container *ngIf="auditStatus">
<span *ngIf="auditStatus.coinbase; else accelerated" class="badge badge-primary mr-1" i18n="tx-features.tag.coinbase|Coinbase">Coinbase</span>
<ng-template #accelerated><span *ngIf="auditStatus.accelerated || accelerationInfo || (tx && tx.acceleration) ; else expected" class="badge badge-accelerated mr-1" i18n="transaction.audit.accelerated">Accelerated</span></ng-template>
<ng-container>
<span *ngIf="auditStatus.coinbase; else expected" class="badge badge-primary mr-1" i18n="tx-features.tag.coinbase|Coinbase">Coinbase</span>
<ng-template #expected><span *ngIf="auditStatus.expected; else seen" class="badge badge-success mr-1" i18n-ngbTooltip="Expected in block tooltip" ngbTooltip="This transaction was projected to be included in the block" placement="bottom" i18n="tx-features.tag.expected|Expected in Block">Expected in Block</span></ng-template>
<ng-template #seen><span *ngIf="auditStatus.seen; else notSeen" class="badge badge-success mr-1" i18n-ngbTooltip="Seen in mempool tooltip" ngbTooltip="This transaction was seen in the mempool prior to mining" placement="bottom" i18n="tx-features.tag.seen|Seen in Mempool">Seen in Mempool</span></ng-template>
<ng-template #notSeen><span class="badge badge-warning mr-1" i18n-ngbTooltip="Not seen in mempool tooltip" ngbTooltip="This transaction was missing from our mempool prior to mining" placement="bottom" i18n="tx-features.tag.not-seen|Not seen in Mempool">Not seen in Mempool</span></ng-template>

View File

@@ -269,7 +269,7 @@ h3 {
.position-container {
position: absolute;
left: 50%;
bottom: 150px;
bottom: 158px;
}
#divider {

View File

@@ -33,7 +33,7 @@
<li ngbNavItem *ngIf="code.codeTemplate.python && network !== 'liquid' && network !== 'liquidtestnet'" role="presentation">
<a ngbNavLink (click)="adjustContainerHeight( $event )" role="tab">Python</a>
<ng-template ngbNavContent>
<div class="subtitle"><ng-container i18n="API Docs code example">Code Example</ng-container> <app-clipboard [text]="wrapEsModule(code)"></app-clipboard></div>
<div class="subtitle"><ng-container i18n="API Docs code example">Code Example</ng-container> <app-clipboard [text]="wrapPythonTemplate(code)"></app-clipboard></div>
<pre><code [innerText]="wrapPythonTemplate(code)"></code></pre>
</ng-template>
</li>

View File

@@ -1,13 +1,16 @@
import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter, NgZone, OnInit } from '@angular/core';
import { SeoService } from '../../services/seo.service';
import { ApiService } from '../../services/api.service';
import { delay, Observable, switchMap, tap, zip } from 'rxjs';
import { delay, Observable, of, switchMap, tap, zip } from 'rxjs';
import { AssetsService } from '../../services/assets.service';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { StateService } from '../../services/state.service';
import { EChartsOption, echarts } from '../../graphs/echarts';
import { isMobile } from '../../shared/common.utils';
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
import { getFlagEmoji } from '../../shared/common.utils';
import { lerpColor } from '../../shared/graphs.utils';
@Component({
selector: 'app-nodes-channels-map',
@@ -50,6 +53,7 @@ export class NodesChannelsMap implements OnInit {
private router: Router,
private zone: NgZone,
private activatedRoute: ActivatedRoute,
private amountShortenerPipe: AmountShortenerPipe,
) {
}
@@ -86,10 +90,12 @@ export class NodesChannelsMap implements OnInit {
return zip(
this.assetsService.getWorldMapJson$,
this.style !== 'channelpage' ? this.apiService.getChannelsGeo$(params.get('public_key') ?? undefined, this.style) : [''],
[params.get('public_key') ?? undefined]
[params.get('public_key') ?? undefined],
this.style === 'widget' ? of(undefined) : this.apiService.getWorldNodes$(),
).pipe(tap((data) => {
echarts.registerMap('world', data[0]);
let maxLiquidity = data[3]?.maxLiquidity;
const channelsLoc = [];
const nodes = [];
const nodesPubkeys = {};
@@ -197,13 +203,24 @@ export class NodesChannelsMap implements OnInit {
this.zoom = -0.05 * distance + 8;
}
this.prepareChartOptions(nodes, channelsLoc);
if (data[3]) {
for (const node of nodes) {
const foundNode = data[3].nodes.find((n) => n[2] === node[3]);
if (foundNode) {
node.push(foundNode[4], foundNode[5], foundNode[6]?.en, foundNode[7]);
maxLiquidity = Math.max(maxLiquidity ?? 0, foundNode[4]);
}
}
}
maxLiquidity = Math.max(1, maxLiquidity);
this.prepareChartOptions(nodes, channelsLoc, maxLiquidity);
}));
})
);
}
prepareChartOptions(nodes, channels) {
prepareChartOptions(nodes, channels, maxLiquidity) {
let title: object;
if (channels.length === 0) {
if (!this.placeholder) {
@@ -267,7 +284,12 @@ export class NodesChannelsMap implements OnInit {
data: nodes,
coordinateSystem: 'geo',
geoIndex: 0,
symbolSize: this.nodeSize,
symbolSize: (params) => {
if (maxLiquidity) {
return 10 * Math.pow(params[5] / maxLiquidity, 0.2) + 3;
}
return this.nodeSize;
},
tooltip: {
show: true,
backgroundColor: 'rgba(17, 19, 31, 1)',
@@ -281,11 +303,25 @@ export class NodesChannelsMap implements OnInit {
formatter: (value) => {
const data = value.data;
const alias = data[4].length > 0 ? data[4] : data[3].slice(0, 20);
return `<b style="color: white">${alias}</b>`;
}
const liquidity = data[5] >= 100000000 ?
`${this.amountShortenerPipe.transform(data[5] / 100000000)} BTC` :
`${this.amountShortenerPipe.transform(data[5], 2)} sats`;
return `
<b style="color: white">${alias}</b><br>
${liquidity}<br>` +
$localize`:@@205c1b86ac1cc419c4d0cca51fdde418c4ffdc20:${data[6]}:INTERPOLATION: channels` + `<br>
${getFlagEmoji(data[8])} ${data[7]}
`;
},
},
itemStyle: {
color: 'white',
color: (params) => {
if (!maxLiquidity) {
return 'white';
}
return `${lerpColor('#1E88E5', '#D81B60', Math.pow(params.data[5] / maxLiquidity, 0.2))}`;
},
opacity: 1,
borderColor: 'black',
borderWidth: 0,
@@ -361,8 +397,6 @@ export class NodesChannelsMap implements OnInit {
}
chartOptions.series[0].itemStyle.borderWidth = nodeBorder;
chartOptions.series[0].symbolSize += e.zoom > 1 ? speed * 15 : -speed * 15;
chartOptions.series[0].symbolSize = Math.max(4, Math.min(7, chartOptions.series[0].symbolSize));
chartOptions.series[1].lineStyle.opacity += e.zoom > 1 ? speed : -speed;
chartOptions.series[1].lineStyle.width += e.zoom > 1 ? speed : -speed;

View File

@@ -225,8 +225,9 @@ export class ApiService {
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos/emergency-spent/stats');
}
listFeaturedAssets$(): Observable<any[]> {
return this.httpClient.get<any[]>(this.apiBaseUrl + '/api/v1/assets/featured');
listFeaturedAssets$(network: string = 'liquid'): Observable<any[]> {
if (network === 'liquid') return this.httpClient.get<any[]>(this.apiBaseUrl + '/api/v1/assets/featured');
return of([]);
}
getAssetGroup$(id: string): Observable<any> {

View File

@@ -1,8 +1,8 @@
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { Inject, Injectable, PLATFORM_ID, makeStateKey, TransferState } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { TransferState, makeStateKey } from '@angular/platform-browser';
import { isPlatformBrowser } from '@angular/common';
@Injectable()

View File

@@ -145,6 +145,7 @@ export class StateService {
hideAudit: BehaviorSubject<boolean>;
fiatCurrency$: BehaviorSubject<string>;
rateUnits$: BehaviorSubject<string>;
showMiningInfo$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
searchFocus$: Subject<boolean> = new Subject<boolean>();
menuOpen$: BehaviorSubject<boolean> = new BehaviorSubject(false);

View File

@@ -7,5 +7,5 @@ if (environment.production) {
enableProdMode();
}
export { AppServerModule } from './app/app.server.module';
export { AppServerModule } from './app/app.module.server';
export { renderModule } from '@angular/platform-server';