Merge branch 'master' into nymkappa/prepaid-acceleration
This commit is contained in:
@@ -263,7 +263,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
||||
imports: [RouterModule.forRoot(routes, {
|
||||
initialNavigation: 'enabledBlocking',
|
||||
scrollPositionRestoration: 'enabled',
|
||||
anchorScrolling: 'enabled',
|
||||
anchorScrolling: 'disabled',
|
||||
preloadingStrategy: AppPreloadingStrategy
|
||||
})],
|
||||
})
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<track label="Português" kind="captions" srclang="pt" src="/resources/promo-video/pt.vtt" [attr.default]="showSubtitles('pt') ? '' : null">
|
||||
</video>
|
||||
|
||||
<ng-container *ngIf="officialMempoolSpace">
|
||||
<ng-container>
|
||||
<app-about-sponsors></app-about-sponsors>
|
||||
</ng-container>
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="officialMempoolSpace">
|
||||
<ng-container>
|
||||
<div *ngIf="profiles$ | async as profiles" id="community-sponsors-anchor">
|
||||
<div class="community-sponsor" style="margin-bottom: 68px" *ngIf="profiles.whales.length > 0">
|
||||
<h3 i18n="about.sponsors.withHeart">Whale Sponsors</h3>
|
||||
|
||||
@@ -52,7 +52,7 @@ export class AccelerateFeeGraphComponent implements OnInit, OnChanges {
|
||||
rate: option.rate,
|
||||
style: this.getStyle(option.rate, maxRate, baseHeight),
|
||||
class: 'max',
|
||||
label: 'maximum',
|
||||
label: $localize`maximum`,
|
||||
active: option.index === this.maxRateIndex,
|
||||
rateIndex: option.index,
|
||||
fee: option.fee,
|
||||
@@ -63,7 +63,7 @@ export class AccelerateFeeGraphComponent implements OnInit, OnChanges {
|
||||
rate: this.estimate.targetFeeRate,
|
||||
style: this.getStyle(this.estimate.targetFeeRate, maxRate, baseHeight),
|
||||
class: 'target',
|
||||
label: 'next block',
|
||||
label: $localize`:@@bdf0e930eb22431140a2eaeacd809cc5f8ebd38c:Next Block`.toLowerCase(),
|
||||
fee: this.estimate.nextBlockFee - this.estimate.txSummary.effectiveFee
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,18 +32,16 @@
|
||||
<div class="alert alert-mempool">You are currently on the waitlist</div>
|
||||
</div>
|
||||
|
||||
<h5>Your transaction</h5>
|
||||
<h5 i18n="accelerator.your-transaction">Your transaction</h5>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<small *ngIf="hasAncestors" class="form-text text-muted mb-2">
|
||||
Plus {{ estimate.txSummary.ancestorCount - 1 }} unconfirmed ancestor{{ estimate.txSummary.ancestorCount > 2 ? 's' : ''}}.
|
||||
<ng-container i18n="accelerator.plus-unconfirmed-ancestors">Plus {{ estimate.txSummary.ancestorCount - 1 }} unconfirmed ancestor(s)</ng-container>
|
||||
</small>
|
||||
<table class="table table-borderless table-border table-dark table-accelerator">
|
||||
<tbody>
|
||||
<tr class="group-first">
|
||||
<td class="item">
|
||||
Virtual size
|
||||
</td>
|
||||
<td class="item" i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
|
||||
<td style="text-align: end;" [innerHTML]="'‎' + (estimate.txSummary.effectiveVsize | vbytes: 2)"></td>
|
||||
</tr>
|
||||
<tr class="info">
|
||||
@@ -52,9 +50,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="item">
|
||||
In-band fees
|
||||
</td>
|
||||
<td class="item" i18n="accelerator.in-band-fees">In-band fees</td>
|
||||
<td style="text-align: end;">
|
||||
{{ estimate.txSummary.effectiveFee | number : '1.0-0' }} <span class="symbol" i18n="shared.sats">sats</span>
|
||||
</td>
|
||||
@@ -69,13 +65,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<h5>How much more are you willing to pay?</h5>
|
||||
<h5 i18n="accelerator.pay-how-much">How much more are you willing to pay?</h5>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<small class="form-text text-muted mb-2">
|
||||
Choose the maximum extra transaction fee you're willing to pay to get into the next block.<br>
|
||||
If the estimated next block rate rises beyond this limit, we will automatically cancel your acceleration request.
|
||||
</small>
|
||||
<small class="form-text text-muted mb-2" i18n="accelerator.transaction-fee-description">Choose the maximum extra transaction fee you're willing to pay to get into the next block.</small>
|
||||
<div class="form-group">
|
||||
<div class="fee-card">
|
||||
<div class="d-flex mb-0">
|
||||
@@ -99,9 +92,7 @@
|
||||
<!-- ESTIMATED FEE -->
|
||||
<ng-container>
|
||||
<tr class="group-first">
|
||||
<td class="item">
|
||||
Next block market rate
|
||||
</td>
|
||||
<td class="item" i18n="accelerator.next-block-rate">Next block market rate</td>
|
||||
<td class="amt" style="font-size: 16px">
|
||||
{{ estimate.targetFeeRate | number : '1.0-0' }}
|
||||
</td>
|
||||
@@ -109,7 +100,7 @@
|
||||
</tr>
|
||||
<tr class="info">
|
||||
<td class="info">
|
||||
<i><small>Estimated extra fee required</small></i>
|
||||
<i><small i18n="accelerator.estimated-extra-fee-required">Estimated extra fee required</small></i>
|
||||
</td>
|
||||
<td class="amt">
|
||||
{{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }}
|
||||
@@ -123,13 +114,11 @@
|
||||
|
||||
<!-- MEMPOOL BASE FEE -->
|
||||
<tr>
|
||||
<td class="item">
|
||||
Mempool Accelerator™ fees
|
||||
</td>
|
||||
<td class="item" i18n="accelerator.mempool-accelerator-fees">Mempool Accelerator™ fees</td>
|
||||
</tr>
|
||||
<tr class="info">
|
||||
<td class="info">
|
||||
<i><small>Accelerator Service Fee</small></i>
|
||||
<i><small i18n="accelerator.service-fee">Accelerator Service Fee</small></i>
|
||||
</td>
|
||||
<td class="amt">
|
||||
+{{ estimate.mempoolBaseFee | number }}
|
||||
@@ -141,7 +130,7 @@
|
||||
</tr>
|
||||
<tr class="info group-last">
|
||||
<td class="info">
|
||||
<i><small>Transaction Size Surcharge</small></i>
|
||||
<i><small i18n="accelerator.tx-size-surcharge">Transaction Size Surcharge</small></i>
|
||||
</td>
|
||||
<td class="amt">
|
||||
+{{ estimate.vsizeFee | number }}
|
||||
@@ -156,7 +145,7 @@
|
||||
<ng-container>
|
||||
<tr class="group-first" style="border-top: 1px dashed grey; border-collapse: collapse;">
|
||||
<td class="item">
|
||||
<b style="background-color: #5E35B1" class="p-1 pl-0">Estimated acceleration cost</b>
|
||||
<b style="background-color: #5E35B1" class="p-1 pl-0" i18n="accelerator.estimated-cost">Estimated acceleration cost</b>
|
||||
</td>
|
||||
<td class="amt">
|
||||
<span style="background-color: #5E35B1" class="p-1 pl-0">
|
||||
@@ -170,7 +159,7 @@
|
||||
</tr>
|
||||
<tr class="info group-last" style="border-bottom: 1px solid lightgrey">
|
||||
<td class="info" colspan=3>
|
||||
<i><small>If your tx is accelerated to </small><small>{{ estimate.targetFeeRate | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></small></i>
|
||||
<i><small><ng-container *ngTemplateOutlet="acceleratedTo; context: {$implicit: estimate.targetFeeRate }"></ng-container></small></i>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
@@ -179,7 +168,7 @@
|
||||
<ng-container>
|
||||
<tr class="group-first">
|
||||
<td class="item">
|
||||
<b style="background-color: #105fb0;" class="p-1 pl-0">Maximum acceleration cost</b>
|
||||
<b style="background-color: #105fb0;" class="p-1 pl-0" i18n="accelerator.maximum-cost">Maximum acceleration cost</b>
|
||||
</td>
|
||||
<td class="amt">
|
||||
<span style="background-color: #105fb0" class="p-1 pl-0">
|
||||
@@ -195,7 +184,7 @@
|
||||
</tr>
|
||||
<tr class="info group-last">
|
||||
<td class="info" colspan=3>
|
||||
<i><small>If your tx is accelerated to </small><small>~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></small></i>
|
||||
<i><small><ng-container *ngTemplateOutlet="acceleratedTo; context: {$implicit: (estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize }"></ng-container></small></i>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
@@ -203,9 +192,7 @@
|
||||
<!-- USER BALANCE -->
|
||||
<ng-container *ngIf="isLoggedIn() && estimate.userBalance < maxCost">
|
||||
<tr class="group-first group-last" style="border-top: 1px dashed grey">
|
||||
<td class="item">
|
||||
Available balance
|
||||
</td>
|
||||
<td class="item" i18n="accelerator.available-balance">Available balance</td>
|
||||
<td class="amt">
|
||||
{{ estimate.userBalance | number }}
|
||||
</td>
|
||||
@@ -224,7 +211,7 @@
|
||||
<td class="item"></td>
|
||||
<td class="amt"></td>
|
||||
<td class="units d-flex">
|
||||
<a [routerLink]="['/login']" [queryParams]="{redirectTo: '/tx/' + tx.txid + '#accelerate'}" class="btn btn-purple flex-grow-1">Login</a>
|
||||
<a [routerLink]="['/login']" [queryParams]="{redirectTo: '/tx/' + tx.txid + '#accelerate'}" class="btn btn-purple flex-grow-1" i18n="shared.sign-in">Sign In</a>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
@@ -233,7 +220,7 @@
|
||||
<td class="item"></td>
|
||||
<td class="amt"></td>
|
||||
<td class="units d-flex">
|
||||
<a [href]="'https://mempool.space/tx/' + tx.txid + '#accelerate'" class="btn btn-purple flex-grow-1">Accelerate on mempool.space</a>
|
||||
<a [href]="'https://mempool.space/tx/' + tx.txid + '#accelerate'" class="btn btn-purple flex-grow-1" i18n="accelerator.accelerate-on-mempoolspace">Accelerate on mempool.space</a>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
@@ -245,7 +232,7 @@
|
||||
<div class="row mb-3" *ngIf="isLoggedIn() && paymentType === 'bitcoin'">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-end" *ngIf="user && estimate.hasAccess">
|
||||
<button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()">Accelerate</button>
|
||||
<button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()" i18n="transaction.accelerate|Accelerate button label">Accelerate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -265,4 +252,6 @@
|
||||
<ng-template #loadingEstimate>
|
||||
<div class="skeleton-loader"></div>
|
||||
<br>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #acceleratedTo let-i i18n="accelerator.accelerated-to-description">If your tx is accelerated to ~{{ i | number : '1.0-0' }} sat/vB</ng-template>
|
||||
@@ -14,14 +14,29 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-menu" *ngIf="menuOpen && cssWidth > 280">
|
||||
<h5>Match</h5>
|
||||
<div class="btn-group btn-group-toggle">
|
||||
<label class="btn btn-xs blue mode-toggle" [class.active]="filterMode === 'and'">
|
||||
<input type="radio" [value]="'all'" fragment="all" (click)="setFilterMode('and')">All
|
||||
</label>
|
||||
<label class="btn btn-xs green mode-toggle" [class.active]="filterMode === 'or'">
|
||||
<input type="radio" [value]="'any'" fragment="any" (click)="setFilterMode('or')">Any
|
||||
</label>
|
||||
<div class="filter-row">
|
||||
<div class="filter-element">
|
||||
<h5 i18n="mempool-goggles.match">Match</h5>
|
||||
<div class="btn-group btn-group-toggle">
|
||||
<label class="btn btn-xs blue mode-toggle" [class.active]="filterMode === 'and'">
|
||||
<input type="radio" [value]="'all'" fragment="all" (click)="setFilterMode('and')"><ng-container i18n>All</ng-container>
|
||||
</label>
|
||||
<label class="btn btn-xs green mode-toggle" [class.active]="filterMode === 'or'">
|
||||
<input type="radio" [value]="'any'" fragment="any" (click)="setFilterMode('or')"><ng-container i18n="mempool-goggles.any">Any</ng-container>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-element">
|
||||
<h5 i18n="mempool-goggles.gradient">Gradient</h5>
|
||||
<div class="btn-group btn-group-toggle">
|
||||
<label class="btn btn-xs yellow mode-toggle" [class.active]="gradientMode === 'fee'">
|
||||
<input type="radio" [value]="'fee'" fragment="default" (click)="setGradientMode('fee')"><ng-container i18n="mempool-goggles.default">Default</ng-container>
|
||||
</label>
|
||||
<label class="btn btn-xs blue mode-toggle" [class.active]="gradientMode === 'age'">
|
||||
<input type="radio" [value]="'age'" fragment="age" (click)="setGradientMode('age')"><ng-container i18n="mempool-goggles.age">Age</ng-container>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngFor="let group of filterGroups;">
|
||||
<h5>{{ group.label }}</h5>
|
||||
|
||||
@@ -45,6 +45,13 @@
|
||||
}
|
||||
|
||||
.filter-menu {
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: start;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.8rem;
|
||||
color: white;
|
||||
@@ -118,6 +125,12 @@
|
||||
background: #1a9436;
|
||||
}
|
||||
}
|
||||
&.yellow {
|
||||
border: solid 1px #bf7815;
|
||||
&.active {
|
||||
background: #bf7815;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(.block-overview-graph:hover) &, &:hover, &:active {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, EventEmitter, Output, HostListener, Input, ChangeDetectorRef, OnChanges, SimpleChanges, OnInit, OnDestroy } from '@angular/core';
|
||||
import { ActiveFilter, FilterGroups, FilterMode, TransactionFilters } from '../../shared/filters.utils';
|
||||
import { ActiveFilter, FilterGroups, FilterMode, GradientMode, TransactionFilters } from '../../shared/filters.utils';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
@@ -22,6 +22,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
||||
activeFilters: string[] = [];
|
||||
filterFlags: { [key: string]: boolean } = {};
|
||||
filterMode: FilterMode = 'and';
|
||||
gradientMode: GradientMode = 'fee';
|
||||
menuOpen: boolean = false;
|
||||
|
||||
constructor(
|
||||
@@ -32,6 +33,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
||||
ngOnInit(): void {
|
||||
this.filterSubscription = this.stateService.activeGoggles$.subscribe((active: ActiveFilter) => {
|
||||
this.filterMode = active.mode;
|
||||
this.gradientMode = active.gradient;
|
||||
for (const key of Object.keys(this.filterFlags)) {
|
||||
this.filterFlags[key] = false;
|
||||
}
|
||||
@@ -39,7 +41,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.filterFlags[key] = !this.disabledFilters[key];
|
||||
}
|
||||
this.activeFilters = [...active.filters.filter(key => !this.disabledFilters[key])];
|
||||
this.onFilterChanged.emit({ mode: active.mode, filters: this.activeFilters });
|
||||
this.onFilterChanged.emit({ mode: active.mode, filters: this.activeFilters, gradient: this.gradientMode });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,8 +59,14 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
setFilterMode(mode): void {
|
||||
this.filterMode = mode;
|
||||
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters });
|
||||
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] });
|
||||
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters, gradient: this.gradientMode });
|
||||
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters], gradient: this.gradientMode });
|
||||
}
|
||||
|
||||
setGradientMode(mode): void {
|
||||
this.gradientMode = mode;
|
||||
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters, gradient: this.gradientMode });
|
||||
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters], gradient: this.gradientMode });
|
||||
}
|
||||
|
||||
toggleFilter(key): void {
|
||||
@@ -81,8 +89,8 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.activeFilters = this.activeFilters.filter(f => f != key);
|
||||
}
|
||||
const booleanFlags = this.getBooleanFlags();
|
||||
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters });
|
||||
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] });
|
||||
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters, gradient: this.gradientMode });
|
||||
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters], gradient: this.gradientMode });
|
||||
}
|
||||
|
||||
getBooleanFlags(): bigint | null {
|
||||
|
||||
@@ -187,7 +187,7 @@ export class BlockHealthGraphComponent implements OnInit {
|
||||
series: data.length === 0 ? undefined : [
|
||||
{
|
||||
zlevel: 0,
|
||||
name: $localize`Health`,
|
||||
name: $localize`:@@d2bcd3296d2850de762fb943060b7e086a893181:Health`,
|
||||
data: data.map(health => ({
|
||||
value: health[2],
|
||||
block: health[1],
|
||||
|
||||
@@ -8,14 +8,11 @@ import { Color, Position } from './sprite-types';
|
||||
import { Price } from '../../services/price.service';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { defaultColorFunction, setOpacity, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils';
|
||||
import { defaultColorFunction, setOpacity, defaultAuditColors, defaultColors, ageColorFunction } from './utils';
|
||||
import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils';
|
||||
import { detectWebGL } from '../../shared/graphs.utils';
|
||||
|
||||
const unmatchedOpacity = 0.2;
|
||||
const unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity));
|
||||
const unmatchedAuditFeeColors = defaultAuditFeeColors.map(c => setOpacity(c, unmatchedOpacity));
|
||||
const unmatchedMarginalFeeColors = defaultMarginalFeeColors.map(c => setOpacity(c, unmatchedOpacity));
|
||||
const unmatchedAuditColors = {
|
||||
censored: setOpacity(defaultAuditColors.censored, unmatchedOpacity),
|
||||
missing: setOpacity(defaultAuditColors.missing, unmatchedOpacity),
|
||||
@@ -46,6 +43,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
@Input() excludeFilters: string[] = [];
|
||||
@Input() filterFlags: bigint | null = null;
|
||||
@Input() filterMode: FilterMode = 'and';
|
||||
@Input() gradientMode: 'fee' | 'age' = 'fee';
|
||||
@Input() relativeTime: number | null;
|
||||
@Input() blockConversion: Price;
|
||||
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
|
||||
@@ -121,21 +119,22 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
this.setHighlightingEnabled(this.auditHighlighting);
|
||||
}
|
||||
if (changes.overrideColor && this.scene) {
|
||||
this.scene.setColorFunction(this.overrideColors);
|
||||
this.scene.setColorFunction(this.getFilterColorFunction(0n, this.gradientMode));
|
||||
}
|
||||
if ((changes.filterFlags || changes.showFilters || changes.filterMode)) {
|
||||
if ((changes.filterFlags || changes.showFilters || changes.filterMode || changes.gradientMode)) {
|
||||
this.setFilterFlags();
|
||||
}
|
||||
}
|
||||
|
||||
setFilterFlags(goggle?: ActiveFilter): void {
|
||||
this.filterMode = goggle?.mode || this.filterMode;
|
||||
this.gradientMode = goggle?.gradient || this.gradientMode;
|
||||
this.activeFilterFlags = goggle?.filters ? toFlags(goggle.filters) : this.filterFlags;
|
||||
if (this.scene) {
|
||||
if (this.activeFilterFlags != null && this.filtersAvailable) {
|
||||
this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags));
|
||||
this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags, this.gradientMode));
|
||||
} else {
|
||||
this.scene.setColorFunction(this.overrideColors);
|
||||
this.scene.setColorFunction(this.getFilterColorFunction(0n, this.gradientMode));
|
||||
}
|
||||
}
|
||||
this.start();
|
||||
@@ -212,6 +211,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
remove = remove.filter(txid => this.scene.txs[txid]);
|
||||
change = change.filter(tx => this.scene.txs[tx.txid]);
|
||||
|
||||
if (this.gradientMode === 'age') {
|
||||
this.scene.updateAllColors();
|
||||
}
|
||||
this.scene.update(add, remove, change, direction, resetLayout);
|
||||
this.start();
|
||||
this.updateSearchHighlight();
|
||||
@@ -548,25 +550,24 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
|
||||
getColorFunction(): ((tx: TxView) => Color) {
|
||||
if (this.filterFlags) {
|
||||
return this.getFilterColorFunction(this.filterFlags);
|
||||
return this.getFilterColorFunction(this.filterFlags, this.gradientMode);
|
||||
} else if (this.activeFilterFlags) {
|
||||
return this.getFilterColorFunction(this.activeFilterFlags);
|
||||
return this.getFilterColorFunction(this.activeFilterFlags, this.gradientMode);
|
||||
} else {
|
||||
return this.overrideColors;
|
||||
return this.getFilterColorFunction(0n, this.gradientMode);
|
||||
}
|
||||
}
|
||||
|
||||
getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) {
|
||||
getFilterColorFunction(flags: bigint, gradient: 'fee' | 'age'): ((tx: TxView) => Color) {
|
||||
return (tx: TxView) => {
|
||||
if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) {
|
||||
return defaultColorFunction(tx);
|
||||
return (gradient === 'age') ? ageColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)) : defaultColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000));
|
||||
} else {
|
||||
return defaultColorFunction(
|
||||
return (gradient === 'age') ? { r: 1, g: 1, b: 1, a: 0.05 } : defaultColorFunction(
|
||||
tx,
|
||||
unmatchedFeeColors,
|
||||
unmatchedAuditFeeColors,
|
||||
unmatchedMarginalFeeColors,
|
||||
unmatchedAuditColors
|
||||
defaultColors.unmatchedfee,
|
||||
unmatchedAuditColors,
|
||||
this.relativeTime || (Date.now() / 1000)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -68,6 +68,10 @@ export default class BlockScene {
|
||||
|
||||
setColorFunction(colorFunction: ((tx: TxView) => Color) | null): void {
|
||||
this.getColor = colorFunction || defaultColorFunction;
|
||||
this.updateAllColors();
|
||||
}
|
||||
|
||||
updateAllColors(): void {
|
||||
this.dirty = true;
|
||||
if (this.initialised && this.scene) {
|
||||
this.updateColors(performance.now(), 50);
|
||||
|
||||
@@ -37,10 +37,36 @@ export function setOpacity(color: Color, opacity: number): Color {
|
||||
};
|
||||
}
|
||||
|
||||
interface ColorPalette {
|
||||
base: Color[],
|
||||
audit: Color[],
|
||||
marginal: Color[],
|
||||
baseLevel: (tx: TxView, rate: number, time: number) => number,
|
||||
}
|
||||
|
||||
// precomputed colors
|
||||
export const defaultFeeColors = mempoolFeeColors.map(hexToColor);
|
||||
export const defaultAuditFeeColors = defaultFeeColors.map((color) => darken(desaturate(color, 0.3), 0.9));
|
||||
export const defaultMarginalFeeColors = defaultFeeColors.map((color) => darken(desaturate(color, 0.8), 1.1));
|
||||
const defaultColors: { [key: string]: ColorPalette } = {
|
||||
fee: {
|
||||
base: mempoolFeeColors.map(hexToColor),
|
||||
audit: [],
|
||||
marginal: [],
|
||||
baseLevel: (tx: TxView, rate: number) => feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1
|
||||
},
|
||||
}
|
||||
for (const key in defaultColors) {
|
||||
const base = defaultColors[key].base;
|
||||
defaultColors[key].audit = base.map((color) => darken(desaturate(color, 0.3), 0.9));
|
||||
defaultColors[key].marginal = base.map((color) => darken(desaturate(color, 0.8), 1.1));
|
||||
defaultColors['unmatched' + key] = {
|
||||
base: defaultColors[key].base.map(c => setOpacity(c, 0.2)),
|
||||
audit: defaultColors[key].audit.map(c => setOpacity(c, 0.2)),
|
||||
marginal: defaultColors[key].marginal.map(c => setOpacity(c, 0.2)),
|
||||
baseLevel: defaultColors[key].baseLevel,
|
||||
};
|
||||
}
|
||||
|
||||
export { defaultColors as defaultColors };
|
||||
|
||||
export const defaultAuditColors = {
|
||||
censored: hexToColor('f344df'),
|
||||
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
||||
@@ -51,22 +77,21 @@ export const defaultAuditColors = {
|
||||
|
||||
export function defaultColorFunction(
|
||||
tx: TxView,
|
||||
feeColors: Color[] = defaultFeeColors,
|
||||
auditFeeColors: Color[] = defaultAuditFeeColors,
|
||||
marginalFeeColors: Color[] = defaultMarginalFeeColors,
|
||||
auditColors: { [status: string]: Color } = defaultAuditColors
|
||||
colors: { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number } = defaultColors.fee,
|
||||
auditColors: { [status: string]: Color } = defaultAuditColors,
|
||||
relativeTime?: number,
|
||||
): Color {
|
||||
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
|
||||
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1;
|
||||
const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1];
|
||||
const levelIndex = colors.baseLevel(tx, rate, relativeTime || (Date.now() / 1000));
|
||||
const levelColor = colors.base[levelIndex] || colors.base[mempoolFeeColors.length - 1];
|
||||
// Normal mode
|
||||
if (!tx.scene?.highlightingEnabled) {
|
||||
if (tx.acc) {
|
||||
return auditColors.accelerated;
|
||||
} else {
|
||||
return feeLevelColor;
|
||||
return levelColor;
|
||||
}
|
||||
return feeLevelColor;
|
||||
return levelColor;
|
||||
}
|
||||
// Block audit
|
||||
switch(tx.status) {
|
||||
@@ -75,7 +100,7 @@ export function defaultColorFunction(
|
||||
case 'missing':
|
||||
case 'sigop':
|
||||
case 'rbf':
|
||||
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
||||
return colors.marginal[levelIndex] || colors.marginal[mempoolFeeColors.length - 1];
|
||||
case 'fresh':
|
||||
case 'freshcpfp':
|
||||
return auditColors.missing;
|
||||
@@ -84,20 +109,37 @@ export function defaultColorFunction(
|
||||
case 'prioritized':
|
||||
return auditColors.prioritized;
|
||||
case 'selected':
|
||||
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
||||
return colors.marginal[levelIndex] || colors.marginal[mempoolFeeColors.length - 1];
|
||||
case 'accelerated':
|
||||
return auditColors.accelerated;
|
||||
case 'found':
|
||||
if (tx.context === 'projected') {
|
||||
return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
|
||||
return colors.audit[levelIndex] || colors.audit[mempoolFeeColors.length - 1];
|
||||
} else {
|
||||
return feeLevelColor;
|
||||
return levelColor;
|
||||
}
|
||||
default:
|
||||
if (tx.acc) {
|
||||
return auditColors.accelerated;
|
||||
} else {
|
||||
return feeLevelColor;
|
||||
return levelColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function ageColorFunction(
|
||||
tx: TxView,
|
||||
colors: { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number } = defaultColors.fee,
|
||||
auditColors: { [status: string]: Color } = defaultAuditColors,
|
||||
relativeTime?: number,
|
||||
): Color {
|
||||
const color = defaultColorFunction(tx, colors, auditColors, relativeTime);
|
||||
|
||||
const ageLevel = (!tx.time ? 0 : (0.8 * Math.tanh((1 / 15) * Math.log2((Math.max(1, 0.6 * ((relativeTime - tx.time) - 60)))))));
|
||||
return {
|
||||
r: color.r,
|
||||
g: color.g,
|
||||
b: color.b,
|
||||
a: color.a * (1 - ageLevel)
|
||||
};
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
<td class="value"><i><app-time kind="span" [time]="time - relativeTime"></app-time></i></td>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'mined'">
|
||||
<td class="label" i18n="transaction.confirmed-after|Transaction confirmed after">Confirmed</td>
|
||||
<td class="label" i18n="transaction.confirmed|Transaction confirmed state">Confirmed</td>
|
||||
<td class="value"><i><app-time kind="span" [time]="relativeTime - time"></app-time></i></td>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
@@ -68,15 +68,15 @@
|
||||
<td class="value">
|
||||
<ng-container [ngSwitch]="tx?.status">
|
||||
<span *ngSwitchCase="'found'" class="badge badge-success" i18n="transaction.audit.match">Match</span>
|
||||
<span *ngSwitchCase="'censored'" class="badge badge-danger" i18n="transaction.audit.removed">Removed</span>
|
||||
<span *ngSwitchCase="'censored'" class="badge badge-danger" i18n="transaction.audit.removed|Transaction removed state">Removed</span>
|
||||
<span *ngSwitchCase="'missing'" class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span>
|
||||
<span *ngSwitchCase="'sigop'" class="badge badge-warning" i18n="transaction.audit.sigop">High sigop count</span>
|
||||
<span *ngSwitchCase="'fresh'" class="badge badge-warning" i18n="transaction.audit.recently-broadcasted">Recently broadcasted</span>
|
||||
<span *ngSwitchCase="'freshcpfp'" class="badge badge-warning" i18n="transaction.audit.recently-cpfped">Recently CPFP'd</span>
|
||||
<span *ngSwitchCase="'added'" class="badge badge-warning" i18n="transaction.audit.added">Added</span>
|
||||
<span *ngSwitchCase="'prioritized'" class="badge badge-warning" i18n="transaction.audit.prioritized">Prioritized</span>
|
||||
<span *ngSwitchCase="'added'" class="badge badge-warning" i18n="tx-features.tag.added|Added">Added</span>
|
||||
<span *ngSwitchCase="'prioritized'" class="badge badge-warning" i18n="tx-features.tag.prioritized|Prioritized">Prioritized</span>
|
||||
<span *ngSwitchCase="'selected'" class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span>
|
||||
<span *ngSwitchCase="'rbf'" class="badge badge-warning" i18n="transaction.audit.conflicting">Conflicting</span>
|
||||
<span *ngSwitchCase="'rbf'" class="badge badge-warning" i18n="tx-features.tag.conflict|Conflict">Conflict</span>
|
||||
<span *ngSwitchCase="'accelerated'" class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
|
||||
@@ -53,13 +53,13 @@
|
||||
<td i18n="block.miner">Miner</td>
|
||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a [attr.data-cy]="'block-details-miner-badge'" placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block?.extras.pool.slug]" class="badge"
|
||||
[class]="!block?.extras.pool.name || block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
[class]="!block?.extras.pool.name || block?.extras.pool.slug === 'unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block?.extras.pool.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||
<span [attr.data-cy]="'block-details-miner-badge'" placement="bottom" class="badge"
|
||||
[class]="!block?.extras.pool.name || block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
[class]="!block?.extras.pool.name || block?.extras.pool.slug === 'unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block?.extras.pool.name }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
@@ -182,13 +182,13 @@
|
||||
<td i18n="block.miner">Miner</td>
|
||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge"
|
||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
[class]="block.extras.pool.slug === 'unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block.extras.pool.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||
<span placement="bottom" class="badge"
|
||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
[class]="block.extras.pool.slug === 'unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block.extras.pool.name }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, OnInit, OnDestroy, ViewChildren, QueryList, Inject, PLATFORM
|
||||
import { Location } from '@angular/common';
|
||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith } from 'rxjs/operators';
|
||||
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter } from 'rxjs/operators';
|
||||
import { Transaction, Vout } from '../../interfaces/electrs.interface';
|
||||
import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest, forkJoin } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
@@ -484,6 +484,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
|
||||
this.oobSubscription = block$.pipe(
|
||||
filter(() => this.stateService.env.PUBLIC_ACCELERATIONS === true && this.stateService.network === ''),
|
||||
switchMap((block) => this.apiService.getAccelerationsByHeight$(block.height)
|
||||
.pipe(
|
||||
map(accelerations => {
|
||||
|
||||
@@ -65,7 +65,7 @@ export class BlocksList implements OnInit {
|
||||
if (!this.widget) {
|
||||
this.websocketService.want(['blocks']);
|
||||
|
||||
this.seoService.setTitle($localize`:@@meta.title.blocks-list:Blocks`);
|
||||
this.seoService.setTitle($localize`:@@8a7b4bd44c0ac71b2e72de0398b303257f7d2f54:Blocks`);
|
||||
this.ogService.setManualOgImage('recent-blocks.jpg');
|
||||
if( this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet' ) {
|
||||
this.seoService.setDescription($localize`:@@meta.description.liquid.blocks:See the most recent Liquid${seoDescriptionNetwork(this.stateService.network)} blocks along with basic stats such as block height, block size, and more.`);
|
||||
|
||||
@@ -13,10 +13,10 @@ import { StateService } from '../../services/state.service';
|
||||
export class FiatSelectorComponent implements OnInit {
|
||||
fiatForm: UntypedFormGroup;
|
||||
currencies = Object.entries(fiatCurrencies).sort((a: any, b: any) => {
|
||||
if (a[1].name < b[1].name) {
|
||||
if (a[1].code < b[1].code) {
|
||||
return -1;
|
||||
}
|
||||
if (a[1].name > b[1].name) {
|
||||
if (a[1].code > b[1].code) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="container-xl">
|
||||
<div class="row text-center" *ngIf="mempoolInfoData$ | async as mempoolInfoData">
|
||||
<div class="col d-none d-sm-block">
|
||||
<span class="txPerSecond" i18n="dashboard.incoming-transactions">Incoming transactions</span>
|
||||
<span class="txPerSecond" i18n="dashboard.incoming-transactions">Incoming Transactions</span>
|
||||
<ng-template [ngIf]="(isLoadingWebSocket$ | async) === false && mempoolInfoData" [ngIfElse]="loadingTransactions">
|
||||
<span *ngIf="(mempoolLoadingStatus$ | async) !== 100; else inSync">
|
||||
<span class="badge badge-pill badge-warning"><ng-container i18n="dashboard.backend-is-synchronizing">Backend is synchronizing</ng-container> ({{ mempoolLoadingStatus$ | async }}%)</span>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<a class="nav-link" [routerLink]="['/assets' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'database']" [fixedWidth]="true" i18n-title="master-page.assets" title="Assets"></fa-icon></a>
|
||||
</li>
|
||||
<li class="nav-item mr-2" routerLinkActive="active" id="btn-docs">
|
||||
<a class="nav-link" [routerLink]="['/docs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="master-page.docs" title="Docs"></fa-icon></a>
|
||||
<a class="nav-link" [routerLink]="['/docs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="documentation.title" title="Documentation"></fa-icon></a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active" id="btn-about">
|
||||
<a class="nav-link" [routerLink]="['/about']" (click)="collapse()"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" i18n-title="master-page.about" title="About"></fa-icon></a>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<th class="timestamp text-left" i18n="shared.date" [ngClass]="{'widget': widget}">Date</th>
|
||||
<th class="expires-in text-left" *ngIf="!widget && showExpiredUtxos === false" i18n="liquid.expires-in">Expires in</th>
|
||||
<th class="expires-in text-left" *ngIf="!widget && showExpiredUtxos === true" i18n="liquid.expired-since">Expired since</th>
|
||||
<th class="is-dust text-right" *ngIf="!widget && showExpiredUtxos === true" i18n="liquid.is-dust">Is Dust</th>
|
||||
<th class="is-dust text-right" *ngIf="!widget && showExpiredUtxos === true" i18n="liquid.dust">Dust</th>
|
||||
</thead>
|
||||
<tbody *ngIf="federationUtxos$ | async as utxos; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||
<ng-container *ngIf="widget; else regularRows">
|
||||
@@ -63,8 +63,11 @@
|
||||
{{ utxo.blocknumber + utxo.timelock - lastReservesBlockUpdate < 0 ? -(utxo.blocknumber + utxo.timelock - lastReservesBlockUpdate) : utxo.blocknumber + utxo.timelock - lastReservesBlockUpdate }} <span i18n="shared.blocks" class="symbol">blocks</span>
|
||||
</td>
|
||||
<td *ngIf="!widget && showExpiredUtxos === true" class="is-dust text-right" [ngStyle]="{ 'color': !utxo.isDust ? '#D81B60' : '' }">
|
||||
<div i18n="shared.yes" *ngIf="utxo.isDust">Yes</div>
|
||||
<div i18n="shared.no" *ngIf="!utxo.isDust">No</div>
|
||||
@if (utxo.isDust) {
|
||||
✔
|
||||
} @else {
|
||||
➖
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
<div class="fee-estimation-container">
|
||||
<div class="item">
|
||||
<div class="card-text">
|
||||
<div class="fee-text credit" i18n-ngbTooltip="liquid.peg-ins-volume-day" ngbTooltip="24h Peg-In Volume" placement="top">+{{ (+pegsVolume[0].volume) / 100000000 | number: '1.2-2' }} <span i18n="shared.addresses">BTC</span></div>
|
||||
<div class="fee-text credit" i18n-ngbTooltip="liquid.peg-ins-volume-day" ngbTooltip="24h Peg-In Volume" placement="top">+{{ (+pegsVolume[0].volume) / 100000000 | number: '1.2-2' }} <span i18n="shared.btc|BTC">BTC</span></div>
|
||||
<div class="fiat">{{ (+pegsVolume[0].number) }} <span i18n="liquid.peg-ins">Peg-Ins</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="card-text">
|
||||
<div class="fee-text debit" i18n-ngbTooltip="liquid.peg-out-volume-day" ngbTooltip="24h Peg-Out Volume" placement="top">{{ (+pegsVolume[1].volume) / 100000000 | number: '1.2-2' }} <span i18n="shared.addresses">BTC</span></div>
|
||||
<div class="fee-text debit" i18n-ngbTooltip="liquid.peg-out-volume-day" ngbTooltip="24h Peg-Out Volume" placement="top">{{ (+pegsVolume[1].volume) / 100000000 | number: '1.2-2' }} <span i18n="shared.btc|BTC">BTC</span></div>
|
||||
<div class="fiat">{{ (+pegsVolume[1].number) }} <span i18n="liquid.peg-outs">Peg-Outs</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -159,7 +159,7 @@ export class ReservesRatioComponent implements OnInit, OnChanges {
|
||||
data: [
|
||||
{
|
||||
value: value,
|
||||
name: 'Assets vs Liabilities'
|
||||
name: $localize`Assets vs Liabilities`
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -15,12 +15,31 @@
|
||||
<div *ngIf="user === undefined" class="profile_image_container"></div>
|
||||
</ng-container>
|
||||
|
||||
<a class="navbar-brand" [ngClass]="{'dual-logos': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
|
||||
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
||||
<div class="subdomain_container">
|
||||
<img [src]="'/api/v1/services/enterprise/images/' + subdomain + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo">
|
||||
</div>
|
||||
</ng-template>
|
||||
<!-- Large screen -->
|
||||
<a class="navbar-brand d-none d-md-flex" [ngClass]="{'dual-logos': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
|
||||
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
||||
<div class="subdomain_container">
|
||||
<img [src]="'/api/v1/services/enterprise/images/' + subdomain + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
||||
</div>
|
||||
<div class="vertical-line"></div>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="{ val: connectionState$ | async } as connectionState">
|
||||
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" viewBox="0 0 500 126" class="mempool-logo" [ngStyle]="{'opacity': connectionState.val === 2 ? 1 : 0.5 }"></app-svg-images>
|
||||
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" viewBox="0 0 500 126"></app-svg-images>
|
||||
<div class="connection-badge">
|
||||
<div class="badge badge-warning" *ngIf="connectionState.val === 0" i18n="master-page.offline">Offline</div>
|
||||
<div class="badge badge-warning" *ngIf="connectionState.val === 1" i18n="master-page.reconnecting">Reconnecting...</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</a>
|
||||
<!-- Mobile -->
|
||||
<a class="navbar-brand d-flex d-md-none justify-content-center" [ngClass]="{'dual-logos': subdomain, 'mr-0': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
|
||||
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
||||
<div class="subdomain_container">
|
||||
<img [src]="'/api/v1/services/enterprise/images/' + subdomain + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
||||
</div>
|
||||
<div class="vertical-line"></div>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="{ val: connectionState$ | async } as connectionState">
|
||||
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" viewBox="0 0 500 126" class="mempool-logo" [ngStyle]="{'opacity': connectionState.val === 2 ? 1 : 0.5 }"></app-svg-images>
|
||||
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" viewBox="0 0 500 126"></app-svg-images>
|
||||
@@ -77,14 +96,14 @@
|
||||
<a class="nav-link" [routerLink]="['/about']" (click)="collapse()"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" i18n-title="master-page.about" title="About"></fa-icon></a>
|
||||
</li>
|
||||
</ul>
|
||||
<app-search-form [hamburgerOpen]="user != null" class="search-form-container" location="top" (searchTriggered)="collapse()"></app-search-form>
|
||||
<app-search-form [hamburgerOpen]="enterpriseInfo === null && user != null" class="search-form-container" location="top" (searchTriggered)="collapse()"></app-search-form>
|
||||
</div>
|
||||
</nav>
|
||||
<app-menu *ngIf="servicesEnabled" [navOpen]="menuOpen" (loggedOut)="onLoggedOut()" (menuToggled)="menuToggled($event)"></app-menu>
|
||||
</header>
|
||||
|
||||
<div class="d-flex" style="overflow: clip">
|
||||
<div *ngIf="!servicesEnabled" class="empty-sidenav"><!-- empty sidenav needed to push footer down the screen --></div>
|
||||
<div class="empty-sidenav"><!-- empty sidenav needed to push footer down the screen --></div>
|
||||
|
||||
<div class="flex-grow-1 d-flex flex-column">
|
||||
<app-testnet-alert *ngIf="network.val === 'testnet' || network.val === 'signet'"></app-testnet-alert>
|
||||
|
||||
@@ -106,6 +106,8 @@ li.nav-item {
|
||||
.dropdown {
|
||||
.dropdown-toggle {
|
||||
width: 62px;
|
||||
height: 36px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,19 +183,30 @@ nav {
|
||||
}
|
||||
|
||||
.subdomain_logo {
|
||||
max-height: 45px;
|
||||
height: 35px;
|
||||
overflow: clip;
|
||||
max-width: 140px;
|
||||
margin: auto;
|
||||
align-self: center;
|
||||
.rounded {
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.subdomain_container {
|
||||
width: 140px;
|
||||
margin-right: 15px;
|
||||
max-width: 140px;
|
||||
text-align: center;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.vertical-line {
|
||||
border-left: 1px solid #444;
|
||||
height: 30px;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.logo-holder {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</span>
|
||||
<a *ngIf="!userAuth" class="d-flex justify-content-center align-items-center nav-link m-0 menu-click" routerLink="/login" role="tab" (click)="onLinkClick('/login')">
|
||||
<fa-icon class="menu-click" [icon]="['fas', 'user-circle']" [fixedWidth]="true" style="font-size: 25px;margin-right: 15px;"></fa-icon>
|
||||
<span class="menu-click" style="font-size: 20px;" i18n="shared.sign-in">Sign in</span>
|
||||
<span class="menu-click" style="font-size: 20px;" i18n="shared.sign-in">Sign In</span>
|
||||
</a>
|
||||
|
||||
<ng-container *ngIf="userMenuGroups$ | async as menuGroups">
|
||||
|
||||
@@ -175,13 +175,15 @@ export class PoolRankingComponent implements OnInit {
|
||||
} as PieSeriesOption);
|
||||
});
|
||||
|
||||
const percentage = totalShareOther.toFixed(2) + '%';
|
||||
|
||||
// 'Other'
|
||||
data.push({
|
||||
itemStyle: {
|
||||
color: '#6b6b6b',
|
||||
},
|
||||
value: totalShareOther,
|
||||
name: 'Other' + (isMobile() ? `` : ` (${totalShareOther.toFixed(2)}%)`),
|
||||
name: $localize`Other (${percentage})`,
|
||||
label: {
|
||||
overflow: 'none',
|
||||
color: '#b1b1b1',
|
||||
@@ -197,7 +199,6 @@ export class PoolRankingComponent implements OnInit {
|
||||
},
|
||||
borderColor: '#000',
|
||||
formatter: () => {
|
||||
const percentage = totalShareOther.toFixed(2) + '%';
|
||||
const i = totalBlockOther.toString();
|
||||
if (this.miningWindowPreference === '24h') {
|
||||
return `<b style="color: white">` + $localize`Other (${percentage})` + `</b><br>` +
|
||||
|
||||
@@ -121,6 +121,7 @@ export class PoolComponent implements OnInit {
|
||||
);
|
||||
|
||||
this.oobFees$ = this.route.params.pipe(map((params) => params.slug)).pipe(
|
||||
filter(() => this.stateService.env.PUBLIC_ACCELERATIONS === true && this.stateService.network === ''),
|
||||
switchMap(slug => {
|
||||
return combineLatest([
|
||||
this.apiService.getAccelerationTotals$(this.slug, '1w'),
|
||||
@@ -209,7 +210,7 @@ export class PoolComponent implements OnInit {
|
||||
legend: {
|
||||
data: [
|
||||
{
|
||||
name: $localize`:mining.hashrate:Hashrate`,
|
||||
name: $localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`,
|
||||
inactiveColor: 'rgb(110, 112, 121)',
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
@@ -263,7 +264,7 @@ export class PoolComponent implements OnInit {
|
||||
series: hashrate.length <= 1 ? undefined : [
|
||||
{
|
||||
zlevel: 1,
|
||||
name: $localize`:mining.hashrate:Hashrate`,
|
||||
name: $localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`,
|
||||
showSymbol: false,
|
||||
symbol: 'none',
|
||||
data: hashrate,
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<td class="td-width" i18n="transaction.status|Transaction Status">Status</td>
|
||||
<td>
|
||||
<span *ngIf="rbfInfo.tx.fullRbf" class="badge badge-info" i18n="transaction.full-rbf">Full RBF</span>
|
||||
<span *ngIf="rbfInfo.tx.rbf; else rbfDisabled" class="badge badge-success" i18n="rbfInfo-features.tag.rbf|RBF">RBF</span>
|
||||
<span *ngIf="rbfInfo.tx.rbf; else rbfDisabled" class="badge badge-success" i18n="tx-features.tag.rbf|RBF">RBF</span>
|
||||
<ng-template #rbfDisabled><span class="badge badge-danger mr-1"><del i18n="tx-features.tag.rbf|RBF">RBF</del></span></ng-template>
|
||||
<span *ngIf="rbfInfo.tx.mined" class="badge badge-success" i18n="transaction.rbf.mined">Mined</span>
|
||||
</td>
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
form {
|
||||
margin-top: 5px;
|
||||
@media (min-width: 564px) {
|
||||
margin-top: 0px;
|
||||
margin-left: 5px;
|
||||
margin-right: -5px;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
@@ -39,7 +37,7 @@ form {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@media (min-width: 768px) {
|
||||
min-width: 400px;
|
||||
min-width: 300px;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
min-width: 142px;
|
||||
|
||||
@@ -71,10 +71,10 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="network === '' && auditStatus">
|
||||
<td class="td-width" i18n="transaction.audit">Audit</td>
|
||||
<td class="td-width" i18n="block.toggle-audit|Toggle Audit">Audit</td>
|
||||
<td *ngIf="pool" class="wrap-cell">
|
||||
<ng-container>
|
||||
<span *ngIf="auditStatus.coinbase; else expected" class="badge badge-primary mr-1" i18n="tx-features.tag.coinbase|Coinbase">Coinbase</span>
|
||||
<span *ngIf="auditStatus.coinbase; else expected" class="badge badge-primary mr-1" i18n="transactions-list.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 *ngIf="!auditStatus.conflict" 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>
|
||||
@@ -103,7 +103,7 @@
|
||||
<ng-container *ngIf="!tx?.status?.confirmed && showAccelerationSummary">
|
||||
<br>
|
||||
<div class="title float-left">
|
||||
<h2>Accelerate</h2>
|
||||
<h2 i18n="transaction.accelerate|Accelerate button label">Accelerate</h2>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-info accelerator-toggle btn-sm float-right" (click)="showAccelerationSummary = false" i18n="hide-accelerator">Hide accelerator</button>
|
||||
<div class="clearfix"></div>
|
||||
@@ -302,7 +302,7 @@
|
||||
<app-transactions-list #txList [transactions]="[tx]" [cached]="isCached" [errorUnblinded]="errorUnblinded" [inputIndex]="inputIndex" [outputIndex]="outputIndex" [transactionPage]="true"></app-transactions-list>
|
||||
|
||||
<div class="title text-left">
|
||||
<h2 i18n="transaction.details">Details</h2>
|
||||
<h2 i18n="transaction.details|Transaction Details">Details</h2>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="row">
|
||||
@@ -462,7 +462,7 @@
|
||||
<br>
|
||||
|
||||
<div class="title">
|
||||
<h2 i18n="transaction.details">Details</h2>
|
||||
<h2 i18n="transaction.details|Transaction Details">Details</h2>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
@@ -562,7 +562,7 @@
|
||||
<td class="td-width" i18n="block.miner">Miner</td>
|
||||
<td *ngIf="pool" class="wrap-cell">
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, pool.slug]" class="badge mr-1"
|
||||
[class]="pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
[class]="pool.slug === 'unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ pool.name }}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -360,7 +360,8 @@
|
||||
<ng-template #auditInProgress>
|
||||
<ng-container *ngIf="(auditStatus$ | async) as auditStatus; else loadingSkeletonLiquid">
|
||||
<div class="in-progress-message" *ngIf="auditStatus.lastBlockAudit && auditStatus.bitcoinHeaders; else loadingSkeletonLiquid">
|
||||
<span i18n="liquid.audit-in-progress">Audit in progress: Bitcoin block height #{{ auditStatus.lastBlockAudit }} / #{{ auditStatus.bitcoinHeaders }}</span>
|
||||
<div i18n="lightning.indexing-in-progress">Indexing in progress</div>
|
||||
<div>#{{ auditStatus.lastBlockAudit }} / #{{ auditStatus.bitcoinHeaders }}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
@@ -7,7 +7,7 @@ import { ApiService } from '../services/api.service';
|
||||
import { StateService } from '../services/state.service';
|
||||
import { WebsocketService } from '../services/websocket.service';
|
||||
import { SeoService } from '../services/seo.service';
|
||||
import { ActiveFilter, FilterMode, toFlags } from '../shared/filters.utils';
|
||||
import { ActiveFilter, FilterMode, GradientMode, toFlags } from '../shared/filters.utils';
|
||||
import { detectWebGL } from '../shared/graphs.utils';
|
||||
|
||||
interface MempoolBlocksData {
|
||||
@@ -74,14 +74,15 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
private lastReservesBlockUpdate: number = 0;
|
||||
|
||||
goggleResolution = 82;
|
||||
goggleCycle: { index: number, name: string, mode: FilterMode, filters: string[] }[] = [
|
||||
{ index: 0, name: 'All', mode: 'and', filters: [] },
|
||||
{ index: 1, name: 'Consolidation', mode: 'and', filters: ['consolidation'] },
|
||||
{ index: 2, name: 'Coinjoin', mode: 'and', filters: ['coinjoin'] },
|
||||
{ index: 3, name: 'Data', mode: 'or', filters: ['inscription', 'fake_pubkey', 'op_return'] },
|
||||
goggleCycle: { index: number, name: string, mode: FilterMode, filters: string[], gradient: GradientMode }[] = [
|
||||
{ index: 0, name: $localize`:@@dfc3c34e182ea73c5d784ff7c8135f087992dac1:All`, mode: 'and', filters: [], gradient: 'fee' },
|
||||
{ index: 1, name: $localize`Consolidation`, mode: 'and', filters: ['consolidation'], gradient: 'fee' },
|
||||
{ index: 2, name: $localize`Coinjoin`, mode: 'and', filters: ['coinjoin'], gradient: 'fee' },
|
||||
{ index: 3, name: $localize`Data`, mode: 'or', filters: ['inscription', 'fake_pubkey', 'op_return'], gradient: 'fee' },
|
||||
];
|
||||
goggleFlags = 0n;
|
||||
goggleMode: FilterMode = 'and';
|
||||
gradientMode: GradientMode = 'fee';
|
||||
goggleIndex = 0;
|
||||
|
||||
private destroy$ = new Subject();
|
||||
@@ -131,6 +132,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
this.goggleIndex = goggle.index;
|
||||
this.goggleFlags = toFlags(goggle.filters);
|
||||
this.goggleMode = goggle.mode;
|
||||
this.gradientMode = goggle.gradient;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -140,6 +142,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
name: 'Custom',
|
||||
mode: active.mode,
|
||||
filters: active.filters,
|
||||
gradient: active.gradient,
|
||||
});
|
||||
this.goggleIndex = this.goggleCycle.length - 1;
|
||||
this.goggleFlags = toFlags(active.filters);
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
</div>
|
||||
<div *ngFor="let item of tabData">
|
||||
<p *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 ) && ( !item.hasOwnProperty('options') || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('officialOnly') && item.options.officialOnly && officialMempoolInstance ))">{{ item.title }}</p>
|
||||
<a *ngIf="( item.type !== 'category' ) && ( item.showConditions.indexOf(network.val) > -1 ) && ( !item.hasOwnProperty('options') || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('officialOnly') && item.options.officialOnly && officialMempoolInstance ) || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('auditOnly') && item.options.auditOnly && auditEnabled ) )" [routerLink]="['./']" fragment="{{ item.fragment }}" (click)="navLinkClick($event)">{{ item.title }}</a>
|
||||
<a *ngIf="( item.type !== 'category' ) && ( item.showConditions.indexOf(network.val) > -1 ) && ( !item.hasOwnProperty('options') || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('officialOnly') && item.options.officialOnly && officialMempoolInstance ) || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('auditOnly') && item.options.auditOnly && auditEnabled ) )" (click)="navLinkClick($event, item.fragment)">{{ item.title }}</a>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ a {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
margin: 5px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#enterprise-cta-desktop {
|
||||
|
||||
@@ -33,8 +33,9 @@ export class ApiDocsNavComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
navLinkClick(event) {
|
||||
this.navLinkClickEvent.emit(event);
|
||||
navLinkClick(event, fragment) {
|
||||
event.preventDefault();
|
||||
this.navLinkClickEvent.emit({event: event, fragment: fragment});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<div *ngIf="!item.hasOwnProperty('options') || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('officialOnly') && item.options.officialOnly && officialMempoolInstance ) || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('auditOnly') && item.options.auditOnly && auditEnabled )">
|
||||
<h3 *ngIf="item.type === 'category'">{{ item.title }}</h3>
|
||||
<div *ngIf="item.type !== 'category'" class="endpoint-container" id="{{ item.fragment }}">
|
||||
<a id="{{ item.fragment + '-tab-header' }}" class="section-header" (click)="anchorLinkClick( $event )" [routerLink]="['./']" fragment="{{ item.fragment }}"><table><tr><td>{{ item.title }}</td><td><span>{{ item.category }}</span></td></tr></table></a>
|
||||
<a id="{{ item.fragment + '-tab-header' }}" class="section-header" (click)="anchorLinkClick({event: $event, fragment: item.fragment})"><table><tr><td>{{ item.title }}</td><td><span>{{ item.category }}</span></td></tr></table></a>
|
||||
<div class="endpoint-content">
|
||||
<ng-container *ngTemplateOutlet="dict[item.fragment]" class="endpoint"></ng-container>
|
||||
</div>
|
||||
@@ -54,7 +54,7 @@
|
||||
<div *ngIf="!item.hasOwnProperty('options') || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('officialOnly') && item.options.officialOnly && officialMempoolInstance )">
|
||||
<h3 *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )">{{ item.title }}</h3>
|
||||
<div *ngIf="( item.type !== 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )" class="endpoint-container" id="{{ item.fragment }}">
|
||||
<a id="{{ item.fragment + '-tab-header' }}" class="section-header" (click)="anchorLinkClick( $event )" [routerLink]="['./']" fragment="{{ item.fragment }}">{{ item.title }} <span>{{ item.category }}</span></a>
|
||||
<a id="{{ item.fragment + '-tab-header' }}" class="section-header" (click)="anchorLinkClick({event: $event, fragment: item.fragment})">{{ item.title }} <span>{{ item.category }}</span></a>
|
||||
<div class="endpoint-content">
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
|
||||
@@ -204,6 +204,7 @@ h3 {
|
||||
margin: 20px 0 20px 0;
|
||||
font-size: 24px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
.endpoint-container .section-header:hover {
|
||||
text-decoration: none;
|
||||
|
||||
@@ -56,7 +56,10 @@ export class ApiDocsComponent implements OnInit, AfterViewInit {
|
||||
if( this.route.snapshot.fragment ) {
|
||||
this.openEndpointContainer( this.route.snapshot.fragment );
|
||||
if (document.getElementById( this.route.snapshot.fragment )) {
|
||||
document.getElementById( this.route.snapshot.fragment ).scrollIntoView();
|
||||
let vOffset = ( window.innerWidth <= 992 ) ? 100 : 60;
|
||||
window.scrollTo({
|
||||
top: document.getElementById( this.route.snapshot.fragment ).offsetTop - vOffset
|
||||
});
|
||||
}
|
||||
}
|
||||
window.addEventListener('scroll', that.onDocScroll, { passive: true });
|
||||
@@ -124,20 +127,13 @@ export class ApiDocsComponent implements OnInit, AfterViewInit {
|
||||
this.desktopDocsNavPosition = ( window.pageYOffset > 115 ) ? "fixed" : "relative";
|
||||
}
|
||||
|
||||
anchorLinkClick( event: any ) {
|
||||
let targetId = "";
|
||||
if( event.target.nodeName === "A" ) {
|
||||
targetId = event.target.hash.substring(1);
|
||||
} else {
|
||||
let element = event.target;
|
||||
while( element.nodeName !== "A" ) {
|
||||
element = element.parentElement;
|
||||
}
|
||||
targetId = element.hash.substring(1);
|
||||
}
|
||||
if( this.route.snapshot.fragment === targetId && document.getElementById( targetId )) {
|
||||
document.getElementById( targetId ).scrollIntoView();
|
||||
}
|
||||
anchorLinkClick( e ) {
|
||||
let targetId = e.fragment;
|
||||
let vOffset = ( window.innerWidth <= 992 ) ? 100 : 60;
|
||||
window.scrollTo({
|
||||
top: document.getElementById( targetId ).offsetTop - vOffset
|
||||
});
|
||||
window.history.pushState({}, null, document.location.href.split("#")[0] + "#" + targetId);
|
||||
this.openEndpointContainer( targetId );
|
||||
}
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@ export interface PoolInfo {
|
||||
emptyBlocks: number;
|
||||
slug: string;
|
||||
poolUniqueId: number;
|
||||
unique_id: number;
|
||||
}
|
||||
export interface PoolStat {
|
||||
pool: PoolInfo;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
|
||||
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators,
|
||||
PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit, Acceleration, AccelerationHistoryParams, CurrentPegs, AuditStatus, FederationAddress, FederationUtxo, RecentPeg, PegsVolume, AccelerationInfo } from '../interfaces/node-api.interface';
|
||||
import { BehaviorSubject, Observable, catchError, filter, of, shareReplay, take, tap } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, catchError, filter, map, of, shareReplay, take, tap } from 'rxjs';
|
||||
import { StateService } from './state.service';
|
||||
import { Transaction } from '../interfaces/electrs.interface';
|
||||
import { Conversion } from './price.service';
|
||||
@@ -246,11 +246,29 @@ export class ApiService {
|
||||
return this.httpClient.get<any>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pools` +
|
||||
(interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
|
||||
)
|
||||
.pipe(
|
||||
map((response) => {
|
||||
response.body.pools.forEach((pool) => {
|
||||
if (pool.poolUniqueId === 0) {
|
||||
pool.name = $localize`:@@e5d8bb389c702588877f039d72178f219453a72d:Unknown`;
|
||||
}
|
||||
});
|
||||
return response;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getPoolStats$(slug: string): Observable<PoolStat> {
|
||||
return this.httpClient.get<PoolStat>(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}`);
|
||||
return this.httpClient.get<PoolStat>(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}`)
|
||||
.pipe(
|
||||
map((poolStats) => {
|
||||
if (poolStats.pool.unique_id === 0) {
|
||||
poolStats.pool.name = $localize`:@@e5d8bb389c702588877f039d72178f219453a72d:Unknown`;
|
||||
}
|
||||
return poolStats;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getPoolHashrate$(slug: string): Observable<any> {
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface Env {
|
||||
SIGNET_BLOCK_AUDIT_START_HEIGHT: number;
|
||||
HISTORICAL_PRICE: boolean;
|
||||
ACCELERATOR: boolean;
|
||||
PUBLIC_ACCELERATIONS: boolean;
|
||||
ADDITIONAL_CURRENCIES: boolean;
|
||||
GIT_COMMIT_HASH_MEMPOOL_SPACE?: string;
|
||||
PACKAGE_JSON_VERSION_MEMPOOL_SPACE?: string;
|
||||
@@ -77,6 +78,7 @@ const defaultEnv: Env = {
|
||||
'SIGNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
||||
'HISTORICAL_PRICE': true,
|
||||
'ACCELERATOR': false,
|
||||
'PUBLIC_ACCELERATIONS': false,
|
||||
'ADDITIONAL_CURRENCIES': false,
|
||||
};
|
||||
|
||||
@@ -152,7 +154,7 @@ export class StateService {
|
||||
searchFocus$: Subject<boolean> = new Subject<boolean>();
|
||||
menuOpen$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
|
||||
activeGoggles$: BehaviorSubject<ActiveFilter> = new BehaviorSubject({ mode: 'and', filters: [] });
|
||||
activeGoggles$: BehaviorSubject<ActiveFilter> = new BehaviorSubject({ mode: 'and', filters: [], gradient: 'fee' });
|
||||
|
||||
constructor(
|
||||
@Inject(PLATFORM_ID) private platformId: any,
|
||||
@@ -262,22 +264,10 @@ export class StateService {
|
||||
// /^\/ starts with a forward slash...
|
||||
// (?:[a-z]{2}(?:-[A-Z]{2})?\/)? optional locale prefix (non-capturing)
|
||||
// (?:preview\/)? optional "preview" prefix (non-capturing)
|
||||
// (testnet|liquidtestnet|liquid|signet)/ network string (captured as networkMatches[1])
|
||||
// (testnet|signet)/ network string (captured as networkMatches[1])
|
||||
// ($|\/) network string must end or end with a slash
|
||||
const networkMatches = url.match(/^\/(?:[a-z]{2}(?:-[A-Z]{2})?\/)?(?:preview\/)?(testnet|liquidtestnet|liquid|signet)($|\/)/);
|
||||
const networkMatches = url.match(/^\/(?:[a-z]{2}(?:-[A-Z]{2})?\/)?(?:preview\/)?(testnet|signet)($|\/)/);
|
||||
switch (networkMatches && networkMatches[1]) {
|
||||
case 'liquid':
|
||||
if (this.network !== 'liquid') {
|
||||
this.network = 'liquid';
|
||||
this.networkChanged$.next('liquid');
|
||||
}
|
||||
return;
|
||||
case 'liquidtestnet':
|
||||
if (this.network !== 'liquidtestnet') {
|
||||
this.network = 'liquidtestnet';
|
||||
this.networkChanged$.next('liquidtestnet');
|
||||
}
|
||||
return;
|
||||
case 'signet':
|
||||
if (this.network !== 'signet') {
|
||||
this.network = 'signet';
|
||||
@@ -285,7 +275,7 @@ export class StateService {
|
||||
}
|
||||
return;
|
||||
case 'testnet':
|
||||
if (this.network !== 'testnet') {
|
||||
if (this.network !== 'testnet' && this.network !== 'liquidtestnet') {
|
||||
if (this.env.BASE_MODULE === 'liquid') {
|
||||
this.network = 'liquidtestnet';
|
||||
this.networkChanged$.next('liquidtestnet');
|
||||
|
||||
@@ -11,9 +11,12 @@ export interface Filter {
|
||||
|
||||
export type FilterMode = 'and' | 'or';
|
||||
|
||||
export type GradientMode = 'fee' | 'age';
|
||||
|
||||
export interface ActiveFilter {
|
||||
mode: FilterMode,
|
||||
filters: string[],
|
||||
gradient: GradientMode,
|
||||
}
|
||||
|
||||
// binary flags for transaction classification
|
||||
@@ -93,15 +96,15 @@ export const TransactionFilters: { [key: string]: Filter } = {
|
||||
cpfp_parent: { key: 'cpfp_parent', label: 'Paid for by child', flag: TransactionFlags.cpfp_parent, important: true, tooltip: true, txPage: false, },
|
||||
cpfp_child: { key: 'cpfp_child', label: 'Pays for parent', flag: TransactionFlags.cpfp_child, important: true, tooltip: true, txPage: false, },
|
||||
replacement: { key: 'replacement', label: 'Replacement', flag: TransactionFlags.replacement, important: true, tooltip: true, txPage: false, },
|
||||
acceleration: window?.['__env']?.ACCELERATOR ? { key: 'acceleration', label: 'Accelerated', flag: TransactionFlags.acceleration, important: false } : undefined,
|
||||
acceleration: window?.['__env']?.ACCELERATOR ? { key: 'acceleration', label: $localize`:@@b484583f0ce10f3341ab36750d05271d9d22c9a1:Accelerated`, flag: TransactionFlags.acceleration, important: false } : undefined,
|
||||
/* data */
|
||||
op_return: { key: 'op_return', label: 'OP_RETURN', flag: TransactionFlags.op_return, important: true, tooltip: true, txPage: true, },
|
||||
fake_pubkey: { key: 'fake_pubkey', label: 'Fake pubkey', flag: TransactionFlags.fake_pubkey, tooltip: true, txPage: true, },
|
||||
inscription: { key: 'inscription', label: 'Inscription', flag: TransactionFlags.inscription, important: true, tooltip: true, txPage: true, },
|
||||
fake_scripthash: { key: 'fake_scripthash', label: 'Fake scripthash', flag: TransactionFlags.fake_scripthash, tooltip: true, txPage: true,},
|
||||
/* heuristics */
|
||||
coinjoin: { key: 'coinjoin', label: 'Coinjoin', flag: TransactionFlags.coinjoin, important: true, tooltip: true, txPage: true, },
|
||||
consolidation: { key: 'consolidation', label: 'Consolidation', flag: TransactionFlags.consolidation, tooltip: true, txPage: true, },
|
||||
coinjoin: { key: 'coinjoin', label: $localize`Coinjoin`, flag: TransactionFlags.coinjoin, important: true, tooltip: true, txPage: true, },
|
||||
consolidation: { key: 'consolidation', label: $localize`Consolidation`, flag: TransactionFlags.consolidation, tooltip: true, txPage: true, },
|
||||
batch_payout: { key: 'batch_payout', label: 'Batch payment', flag: TransactionFlags.batch_payout, tooltip: true, txPage: true, },
|
||||
/* sighash */
|
||||
sighash_all: { key: 'sighash_all', label: 'sighash_all', flag: TransactionFlags.sighash_all },
|
||||
@@ -112,10 +115,10 @@ export const TransactionFilters: { [key: string]: Filter } = {
|
||||
};
|
||||
|
||||
export const FilterGroups: { label: string, filters: Filter[]}[] = [
|
||||
{ label: 'Features', filters: ['rbf', 'no_rbf', 'v1', 'v2', 'v3', 'nonstandard'] },
|
||||
{ label: 'Address Types', filters: ['p2pk', 'p2ms', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'] },
|
||||
{ label: 'Behavior', filters: ['cpfp_parent', 'cpfp_child', 'replacement', 'acceleration'] },
|
||||
{ label: 'Data', filters: ['op_return', 'fake_pubkey', 'fake_scripthash', 'inscription'] },
|
||||
{ label: 'Heuristics', filters: ['coinjoin', 'consolidation', 'batch_payout'] },
|
||||
{ label: 'Sighash Flags', filters: ['sighash_all', 'sighash_none', 'sighash_single', 'sighash_default', 'sighash_acp'] },
|
||||
{ label: $localize`:@@885666551418fd59011ceb09d5c481095940193b:Features`, filters: ['rbf', 'no_rbf', 'v1', 'v2', 'v3', 'nonstandard'] },
|
||||
{ label: $localize`Address Types`, filters: ['p2pk', 'p2ms', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'] },
|
||||
{ label: $localize`Behavior`, filters: ['cpfp_parent', 'cpfp_child', 'replacement', 'acceleration'] },
|
||||
{ label: $localize`Data`, filters: ['op_return', 'fake_pubkey', 'fake_scripthash', 'inscription'] },
|
||||
{ label: $localize`Heuristics`, filters: ['coinjoin', 'consolidation', 'batch_payout'] },
|
||||
{ label: $localize`Sighash Flags`, filters: ['sighash_all', 'sighash_none', 'sighash_single', 'sighash_default', 'sighash_acp'] },
|
||||
].map(group => ({ label: group.label, filters: group.filters.map(filter => TransactionFilters[filter] || null).filter(f => f != null) }));
|
||||
Reference in New Issue
Block a user