Merge branch 'master' into nymkappa/tx-overflow
This commit is contained in:
@@ -31,6 +31,14 @@
|
||||
<track label="Português" kind="captions" srclang="pt" src="/resources/promo-video/pt.vtt" [attr.default]="showSubtitles('pt') ? '' : null">
|
||||
</video>
|
||||
|
||||
<ng-container *ngIf="false && officialMempoolSpace">
|
||||
<h3 class="mt-5">Sponsor the project</h3>
|
||||
<div class="d-flex justify-content-center" style="max-width: 90%; margin: 35px auto 75px auto; column-gap: 15px">
|
||||
<a href="/sponsor" class="btn" style="background-color: rgba(152, 88, 255, 0.75); box-shadow: 0px 0px 50px 5px rgba(152, 88, 255, 0.75)" i18n="about.community-sponsor-button">Community</a>
|
||||
<a href="/enterprise" class="btn" style="background-color: rgba(152, 88, 255, 0.75); box-shadow: 0px 0px 50px 5px rgba(152, 88, 255, 0.75)" i18n="about.enterprise-sponsor-button">Enterprise</a>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="enterprise-sponsor" id="enterprise-sponsors">
|
||||
<h3 i18n="about.sponsors.enterprise.withRocket">Enterprise Sponsors 🚀</h3>
|
||||
<div class="wrapper">
|
||||
@@ -191,16 +199,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="community-sponsor" id="community-sponsors">
|
||||
<h3 i18n="about.sponsors.withHeart">Community Sponsors ❤️</h3>
|
||||
<ng-container *ngIf="officialMempoolSpace">
|
||||
<div *ngIf="profiles$ | async as profiles" id="community-sponsors">
|
||||
<div class="community-sponsor" style="margin-bottom: 68px" *ngIf="profiles.whales.length > 0">
|
||||
<h3 i18n="about.sponsors.withHeart">Whale Sponsors</h3>
|
||||
<div class="wrapper">
|
||||
<ng-container>
|
||||
<ng-template ngFor let-sponsor [ngForOf]="profiles.whales">
|
||||
<a [href]="'https://twitter.com/' + sponsor.username" target="_blank" rel="sponsored" [title]="sponsor.username">
|
||||
<img class="image" [src]="'data:' + sponsor.image_mime + ';base64,' + sponsor.image" onError="this.src = '/resources/profile/unknown.svg'; this.className = 'image unknown'"/>
|
||||
</a>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="community-sponsor" style="margin-bottom: 68px" *ngIf="profiles.chads.length > 0">
|
||||
<h3 i18n="about.sponsors.withHeart">Chad Sponsors</h3>
|
||||
<div class="wrapper">
|
||||
<ng-template ngFor let-sponsor [ngForOf]="profiles.chads">
|
||||
<a [href]="'https://twitter.com/' + sponsor.username" target="_blank" rel="sponsored" [title]="sponsor.username">
|
||||
<img class="image" [src]="'data:' + sponsor.image_mime + ';base64,' + sponsor.image" onError="this.src = '/resources/profile/unknown.svg'; this.className = 'image unknown'"/>
|
||||
</a>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="community-sponsor" style="margin-bottom: 68px">
|
||||
<h3 i18n="about.sponsors.withHeart">OG Sponsors ❤️</h3>
|
||||
<div class="wrapper">
|
||||
<ng-container *ngIf="sponsors$ | async as sponsors; else loadingSponsors">
|
||||
<ng-template ngFor let-sponsor [ngForOf]="sponsors">
|
||||
<a [href]="'https://twitter.com/' + sponsor.handle" target="_blank" rel="sponsored" [title]="sponsor.handle">
|
||||
<img class="image" [src]="'/api/v1/donations/images/' + sponsor.handle" />
|
||||
</a>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="ogs$ | async as ogs; else loadingSponsors">
|
||||
<a *ngFor="let ogSponsor of ogs" [href]="'https://twitter.com/' + ogSponsor.handle" target="_blank" rel="sponsored" [title]="ogSponsor.handle">
|
||||
<img class="image" [src]="'/api/v1/donations/images/' + ogSponsor.handle" onError="this.src = '/resources/profile/unknown.svg'; this.className = 'image unknown'"/>
|
||||
</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
@@ -340,7 +373,7 @@
|
||||
<div class="wrapper">
|
||||
<ng-template ngFor let-translator [ngForOf]="translators">
|
||||
<a [href]="'https://twitter.com/' + translator.value" target="_blank" [title]="translator.key">
|
||||
<img class="image" [src]="'/api/v1/translators/images/' + translator.value" />
|
||||
<img class="image" [src]="'/api/v1/translators/images/' + translator.value" onError="this.src = '/resources/profile/unknown.svg'; this.className = 'image unknown'"/>
|
||||
</a>
|
||||
</ng-template>
|
||||
</div>
|
||||
@@ -354,7 +387,7 @@
|
||||
<div class="wrapper">
|
||||
<ng-template ngFor let-contributor [ngForOf]="contributors.regular">
|
||||
<a [href]="'https://github.com/' + contributor.name" target="_blank" [title]="contributor.name">
|
||||
<img class="image" [src]="'/api/v1/contributors/images/' + contributor.id" />
|
||||
<img class="image" [src]="'/api/v1/contributors/images/' + contributor.id" onError="this.src = '/resources/profile/unknown.svg'; this.className = 'image unknown'"/>
|
||||
<span>{{ contributor.name }}</span>
|
||||
</a>
|
||||
</ng-template>
|
||||
@@ -366,7 +399,7 @@
|
||||
<div class="wrapper">
|
||||
<ng-template ngFor let-contributor [ngForOf]="contributors.core">
|
||||
<a [href]="'https://github.com/' + contributor.name" target="_blank" [title]="contributor.name">
|
||||
<img class="image" [src]="'/api/v1/contributors/images/' + contributor.id" />
|
||||
<img class="image" [src]="'/api/v1/contributors/images/' + contributor.id" onError="this.src = '/resources/profile/unknown.svg'; this.className = 'image unknown'"/>
|
||||
<span>{{ contributor.name }}</span>
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
margin: 25px;
|
||||
line-height: 32px;
|
||||
}
|
||||
.unknown {
|
||||
border: 1px solid #b4b4b4;
|
||||
}
|
||||
|
||||
.image.not-rounded {
|
||||
border-radius: 0;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Observable } from 'rxjs';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { IBackendInfo } from '../../interfaces/websocket.interface';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { map, share, tap } from 'rxjs/operators';
|
||||
import { ITranslators } from '../../interfaces/node-api.interface';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
|
||||
@@ -19,14 +19,16 @@ import { DOCUMENT } from '@angular/common';
|
||||
export class AboutComponent implements OnInit {
|
||||
@ViewChild('promoVideo') promoVideo: ElementRef;
|
||||
backendInfo$: Observable<IBackendInfo>;
|
||||
sponsors$: Observable<any>;
|
||||
translators$: Observable<ITranslators>;
|
||||
allContributors$: Observable<any>;
|
||||
frontendGitCommitHash = this.stateService.env.GIT_COMMIT_HASH;
|
||||
packetJsonVersion = this.stateService.env.PACKAGE_JSON_VERSION;
|
||||
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
|
||||
showNavigateToSponsor = false;
|
||||
|
||||
profiles$: Observable<any>;
|
||||
translators$: Observable<ITranslators>;
|
||||
allContributors$: Observable<any>;
|
||||
ogs$: Observable<any>;
|
||||
|
||||
constructor(
|
||||
private websocketService: WebsocketService,
|
||||
private seoService: SeoService,
|
||||
@@ -43,10 +45,13 @@ export class AboutComponent implements OnInit {
|
||||
this.seoService.setTitle($localize`:@@004b222ff9ef9dd4771b777950ca1d0e4cd4348a:About`);
|
||||
this.websocketService.want(['blocks']);
|
||||
|
||||
this.sponsors$ = this.apiService.getDonation$()
|
||||
.pipe(
|
||||
tap(() => this.goToAnchor())
|
||||
);
|
||||
this.profiles$ = this.apiService.getAboutPageProfiles$().pipe(
|
||||
tap(() => {
|
||||
this.goToAnchor()
|
||||
}),
|
||||
share(),
|
||||
)
|
||||
|
||||
this.translators$ = this.apiService.getTranslators$()
|
||||
.pipe(
|
||||
map((translators) => {
|
||||
@@ -59,6 +64,9 @@ export class AboutComponent implements OnInit {
|
||||
}),
|
||||
tap(() => this.goToAnchor())
|
||||
);
|
||||
|
||||
this.ogs$ = this.apiService.getOgs$();
|
||||
|
||||
this.allContributors$ = this.apiService.getContributor$().pipe(
|
||||
map((contributors) => {
|
||||
return {
|
||||
|
||||
@@ -147,7 +147,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
}
|
||||
}
|
||||
|
||||
update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined }[], direction: string = 'left', resetLayout: boolean = false): void {
|
||||
update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined, acc: boolean | undefined }[], direction: string = 'left', resetLayout: boolean = false): void {
|
||||
if (this.scene) {
|
||||
this.scene.update(add, remove, change, direction, resetLayout);
|
||||
this.start();
|
||||
|
||||
@@ -150,7 +150,7 @@ export default class BlockScene {
|
||||
this.updateAll(startTime, 200, direction);
|
||||
}
|
||||
|
||||
update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined }[], direction: string = 'left', resetLayout: boolean = false): void {
|
||||
update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined, acc: boolean | undefined }[], direction: string = 'left', resetLayout: boolean = false): void {
|
||||
const startTime = performance.now();
|
||||
const removed = this.removeBatch(remove, startTime, direction);
|
||||
|
||||
@@ -175,6 +175,7 @@ export default class BlockScene {
|
||||
// update effective rates
|
||||
change.forEach(tx => {
|
||||
if (this.txs[tx.txid]) {
|
||||
this.txs[tx.txid].acc = tx.acc;
|
||||
this.txs[tx.txid].feerate = tx.rate || (this.txs[tx.txid].fee / this.txs[tx.txid].vsize);
|
||||
this.txs[tx.txid].rate = tx.rate;
|
||||
this.txs[tx.txid].dirty = true;
|
||||
|
||||
@@ -17,6 +17,7 @@ const auditColors = {
|
||||
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
||||
added: hexToColor('0099ff'),
|
||||
selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
|
||||
accelerated: hexToColor('8F5FF6'),
|
||||
};
|
||||
|
||||
// convert from this class's update format to TxSprite's update format
|
||||
@@ -37,8 +38,9 @@ export default class TxView implements TransactionStripped {
|
||||
vsize: number;
|
||||
value: number;
|
||||
feerate: number;
|
||||
acc?: boolean;
|
||||
rate?: number;
|
||||
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf';
|
||||
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated';
|
||||
context?: 'projected' | 'actual';
|
||||
scene?: BlockScene;
|
||||
|
||||
@@ -63,6 +65,7 @@ export default class TxView implements TransactionStripped {
|
||||
this.vsize = tx.vsize;
|
||||
this.value = tx.value;
|
||||
this.feerate = tx.rate || (tx.fee / tx.vsize); // sort by effective fee rate where available
|
||||
this.acc = tx.acc;
|
||||
this.rate = tx.rate;
|
||||
this.status = tx.status;
|
||||
this.initialised = false;
|
||||
@@ -199,6 +202,11 @@ export default class TxView implements TransactionStripped {
|
||||
const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1];
|
||||
// Normal mode
|
||||
if (!this.scene?.highlightingEnabled) {
|
||||
if (this.acc) {
|
||||
return auditColors.accelerated;
|
||||
} else {
|
||||
return feeLevelColor;
|
||||
}
|
||||
return feeLevelColor;
|
||||
}
|
||||
// Block audit
|
||||
@@ -216,6 +224,8 @@ export default class TxView implements TransactionStripped {
|
||||
return auditColors.added;
|
||||
case 'selected':
|
||||
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
||||
case 'accelerated':
|
||||
return auditColors.accelerated;
|
||||
case 'found':
|
||||
if (this.context === 'projected') {
|
||||
return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
|
||||
@@ -223,7 +233,11 @@ export default class TxView implements TransactionStripped {
|
||||
return feeLevelColor;
|
||||
}
|
||||
default:
|
||||
return feeLevelColor;
|
||||
if (this.acc) {
|
||||
return auditColors.accelerated;
|
||||
} else {
|
||||
return feeLevelColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="effectiveRate && effectiveRate !== feeRate">
|
||||
<td class="td-width" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
|
||||
<td *ngIf="!this.acceleration" class="td-width" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
|
||||
<td *ngIf="this.acceleration" class="td-width" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Accelerated fee rate</td>
|
||||
<td>
|
||||
<app-fee-rate [fee]="effectiveRate"></app-fee-rate>
|
||||
</td>
|
||||
@@ -54,6 +55,7 @@
|
||||
<td *ngSwitchCase="'added'"><span class="badge badge-warning" i18n="transaction.audit.added">Added</span></td>
|
||||
<td *ngSwitchCase="'selected'"><span class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span></td>
|
||||
<td *ngSwitchCase="'rbf'"><span class="badge badge-warning" i18n="transaction.audit.conflicting">Conflicting</span></td>
|
||||
<td *ngSwitchCase="'accelerated'"><span class="badge badge-success" i18n="transaction.audit.accelerated">Accelerated</span></td>
|
||||
</ng-container>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -21,6 +21,7 @@ export class BlockOverviewTooltipComponent implements OnChanges {
|
||||
vsize = 1;
|
||||
feeRate = 0;
|
||||
effectiveRate;
|
||||
acceleration;
|
||||
|
||||
tooltipPosition: Position = { x: 0, y: 0 };
|
||||
|
||||
@@ -53,6 +54,7 @@ export class BlockOverviewTooltipComponent implements OnChanges {
|
||||
this.vsize = tx.vsize || 1;
|
||||
this.feeRate = this.fee / this.vsize;
|
||||
this.effectiveRate = tx.rate;
|
||||
this.acceleration = tx.acc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,12 +340,16 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
const isFresh = {};
|
||||
const isSigop = {};
|
||||
const isRbf = {};
|
||||
const isAccelerated = {};
|
||||
this.numMissing = 0;
|
||||
this.numUnexpected = 0;
|
||||
|
||||
if (blockAudit?.template) {
|
||||
for (const tx of blockAudit.template) {
|
||||
inTemplate[tx.txid] = true;
|
||||
if (tx.acc) {
|
||||
isAccelerated[tx.txid] = true;
|
||||
}
|
||||
}
|
||||
for (const tx of transactions) {
|
||||
inBlock[tx.txid] = true;
|
||||
@@ -365,6 +369,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
for (const txid of blockAudit.fullrbfTxs || []) {
|
||||
isRbf[txid] = true;
|
||||
}
|
||||
for (const txid of blockAudit.acceleratedTxs || []) {
|
||||
isAccelerated[txid] = true;
|
||||
}
|
||||
// set transaction statuses
|
||||
for (const tx of blockAudit.template) {
|
||||
tx.context = 'projected';
|
||||
@@ -389,6 +396,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
isMissing[tx.txid] = true;
|
||||
this.numMissing++;
|
||||
}
|
||||
if (isAccelerated[tx.txid]) {
|
||||
tx.status = 'accelerated';
|
||||
}
|
||||
}
|
||||
for (const [index, tx] of transactions.entries()) {
|
||||
tx.context = 'actual';
|
||||
@@ -405,6 +415,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
isSelected[tx.txid] = true;
|
||||
this.numUnexpected++;
|
||||
}
|
||||
if (isAccelerated[tx.txid]) {
|
||||
tx.status = 'accelerated';
|
||||
}
|
||||
}
|
||||
for (const tx of transactions) {
|
||||
inBlock[tx.txid] = true;
|
||||
|
||||
@@ -4,38 +4,56 @@
|
||||
class="difficulty-tooltip"
|
||||
[style.visibility]="status ? 'visible' : 'hidden'"
|
||||
[style.left]="tooltipPosition.x + 'px'"
|
||||
[style.top]="tooltipPosition.y + 'px'"
|
||||
[style.top]="tooltipPosition.y + (isMobile ? -60 : 0) + 'px'"
|
||||
>
|
||||
<ng-container [ngSwitch]="status">
|
||||
<ng-container *ngIf="!isMobile" [ngSwitch]="status">
|
||||
<ng-container *ngSwitchCase="'mined'">
|
||||
<ng-container *ngIf="isAhead">
|
||||
<ng-container *ngTemplateOutlet="expected === 1 ? blocksSingular : blocksPlural; context: {$implicit: expected }"></ng-container>
|
||||
<ng-template #blocksPlural let-i i18n="difficulty-box.expected-blocks">{{ i }} blocks expected</ng-template>
|
||||
<ng-template #blocksSingular let-i i18n="difficulty-box.expected-block">{{ i }} block expected</ng-template>
|
||||
<ng-container *ngTemplateOutlet="expected === 1 ? expectedMinedBlocksSingular : expectedMinedBlocksPlural; context: {$implicit: expected }"></ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!isAhead">
|
||||
<ng-container *ngTemplateOutlet="mined === 1 ? blocksSingular : blocksPlural; context: {$implicit: mined }"></ng-container>
|
||||
<ng-template #blocksPlural let-i i18n="difficulty-box.mined-blocks">{{ i }} blocks mined</ng-template>
|
||||
<ng-template #blocksSingular let-i i18n="difficulty-box.mined-block">{{ i }} block mined</ng-template>
|
||||
<ng-container *ngTemplateOutlet="mined === 1 ? minedBlocksSingular : minedBlocksPlural; context: {$implicit: mined }"></ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'remaining'">
|
||||
<ng-container *ngTemplateOutlet="remaining === 1 ? blocksSingular : blocksPlural; context: {$implicit: remaining }"></ng-container>
|
||||
<ng-template #blocksPlural let-i i18n="difficulty-box.remaining-blocks">{{ i }} blocks remaining</ng-template>
|
||||
<ng-template #blocksSingular let-i i18n="difficulty-box.remaining-block">{{ i }} block remaining</ng-template>
|
||||
<ng-container *ngTemplateOutlet="remaining === 1 ? remainingBlocksSingular : remainingBlocksPlural; context: {$implicit: remaining }"></ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'ahead'">
|
||||
<ng-container *ngTemplateOutlet="ahead === 1 ? blocksSingular : blocksPlural; context: {$implicit: ahead }"></ng-container>
|
||||
<ng-template #blocksPlural let-i i18n="difficulty-box.blocks-ahead">{{ i }} blocks ahead</ng-template>
|
||||
<ng-template #blocksSingular let-i i18n="difficulty-box.block-ahead">{{ i }} block ahead</ng-template>
|
||||
<ng-container *ngTemplateOutlet="ahead === 1 ? aheadBlocksSingular : aheadBlocksPlural; context: {$implicit: ahead }"></ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'behind'">
|
||||
<ng-container *ngTemplateOutlet="behind === 1 ? blocksSingular : blocksPlural; context: {$implicit: behind }"></ng-container>
|
||||
<ng-template #blocksPlural let-i i18n="difficulty-box.blocks-behind">{{ i }} blocks behind</ng-template>
|
||||
<ng-template #blocksSingular let-i i18n="difficulty-box.block-behind">{{ i }} block behind</ng-template>
|
||||
<ng-container *ngTemplateOutlet="behind === 1 ? behindBlocksSingular : behindBlocksPlural; context: {$implicit: behind }"></ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'next'">
|
||||
<span class="next-block" i18n="@@bdf0e930eb22431140a2eaeacd809cc5f8ebd38c">Next Block</span>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-container *ngIf="isMobile">
|
||||
<ng-container *ngIf="isAhead">
|
||||
<ng-container *ngTemplateOutlet="expected === 1 ? minedBlocksSingular : minedBlocksPlural; context: {$implicit: expected }"></ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!isAhead">
|
||||
<ng-container *ngTemplateOutlet="mined === 1 ? minedBlocksSingular : minedBlocksPlural; context: {$implicit: mined }"></ng-container>
|
||||
</ng-container>
|
||||
<br>
|
||||
<ng-container *ngTemplateOutlet="remaining === 1 ? remainingBlocksSingular : remainingBlocksPlural; context: {$implicit: remaining }"></ng-container>
|
||||
<br>
|
||||
<ng-container *ngIf="ahead > 0">
|
||||
<ng-container *ngTemplateOutlet="ahead === 1 ? aheadBlocksSingular : aheadBlocksPlural; context: {$implicit: ahead }"></ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="behind > 0">
|
||||
<ng-container *ngTemplateOutlet="behind === 1 ? behindBlocksSingular : behindBlocksPlural; context: {$implicit: behind }"></ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<ng-template #expectedMinedBlocksPlural let-i i18n="difficulty-box.expected-blocks">{{ i }} blocks expected</ng-template>
|
||||
<ng-template #expectedMinedBlocksSingular let-i i18n="difficulty-box.expected-block">{{ i }} block expected</ng-template>
|
||||
<ng-template #minedBlocksPlural let-i i18n="difficulty-box.mined-blocks">{{ i }} blocks mined</ng-template>
|
||||
<ng-template #minedBlocksSingular let-i i18n="difficulty-box.mined-block">{{ i }} block mined</ng-template>
|
||||
<ng-template #remainingBlocksPlural let-i i18n="difficulty-box.remaining-blocks">{{ i }} blocks remaining</ng-template>
|
||||
<ng-template #remainingBlocksSingular let-i i18n="difficulty-box.remaining-block">{{ i }} block remaining</ng-template>
|
||||
<ng-template #aheadBlocksPlural let-i i18n="difficulty-box.blocks-ahead">{{ i }} blocks ahead</ng-template>
|
||||
<ng-template #aheadBlocksSingular let-i i18n="difficulty-box.block-ahead">{{ i }} block ahead</ng-template>
|
||||
<ng-template #behindBlocksPlural let-i i18n="difficulty-box.blocks-behind">{{ i }} blocks behind</ng-template>
|
||||
<ng-template #behindBlocksSingular let-i i18n="difficulty-box.block-behind">{{ i }} block behind</ng-template>
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ElementRef, ViewChild, Input, OnChanges } from '@angular/core';
|
||||
import { Component, ElementRef, ViewChild, Input, OnChanges, HostListener } from '@angular/core';
|
||||
|
||||
interface EpochProgress {
|
||||
base: string;
|
||||
@@ -35,12 +35,15 @@ export class DifficultyTooltipComponent implements OnChanges {
|
||||
remaining: number;
|
||||
isAhead: boolean;
|
||||
isBehind: boolean;
|
||||
isMobile: boolean;
|
||||
|
||||
tooltipPosition = { x: 0, y: 0 };
|
||||
|
||||
@ViewChild('tooltip') tooltipElement: ElementRef<HTMLCanvasElement>;
|
||||
|
||||
constructor() {}
|
||||
constructor() {
|
||||
this.onResize();
|
||||
}
|
||||
|
||||
ngOnChanges(changes): void {
|
||||
if (changes.cursorPosition && changes.cursorPosition.currentValue) {
|
||||
@@ -63,4 +66,9 @@ export class DifficultyTooltipComponent implements OnChanges {
|
||||
this.isBehind = this.behind > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(): void {
|
||||
this.isMobile = window.innerWidth <= 767.98;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div 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 class="epoch-blocks" height="22px" width="100%" viewBox="0 0 224 9" shape-rendering="crispEdges" preserveAspectRatio="none">
|
||||
<svg #epochSvg class="epoch-blocks" height="22px" width="100%" viewBox="0 0 224 9" shape-rendering="crispEdges" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<linearGradient id="diff-gradient" x1="0%" y1="0%" x2="100%" y2="0%" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stop-color="#105fb0" />
|
||||
@@ -22,7 +22,7 @@
|
||||
class="rect {{rect.status}}"
|
||||
[class.hover]="hoverSection && rect.status === hoverSection.status"
|
||||
(pointerover)="onHover($event, rect);"
|
||||
(pointerout)="onBlur($event);"
|
||||
(pointerout)="onBlur();"
|
||||
>
|
||||
<animate
|
||||
*ngIf="rect.status === 'next'"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
|
||||
import { combineLatest, Observable, timer } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, ElementRef, ViewChild, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
|
||||
import { combineLatest, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { StateService } from '../..//services/state.service';
|
||||
|
||||
interface EpochProgress {
|
||||
@@ -44,6 +44,8 @@ export class DifficultyComponent implements OnInit {
|
||||
@Input() showProgress = true;
|
||||
@Input() showHalving = false;
|
||||
@Input() showTitle = true;
|
||||
|
||||
@ViewChild('epochSvg') epochSvgElement: ElementRef<SVGElement>;
|
||||
|
||||
isLoadingWebSocket$: Observable<boolean>;
|
||||
difficultyEpoch$: Observable<EpochProgress>;
|
||||
@@ -191,21 +193,26 @@ export class DifficultyComponent implements OnInit {
|
||||
}
|
||||
|
||||
@HostListener('pointerdown', ['$event'])
|
||||
onPointerDown(event) {
|
||||
this.onPointerMove(event);
|
||||
onPointerDown(event): void {
|
||||
if (this.epochSvgElement.nativeElement?.contains(event.target)) {
|
||||
this.onPointerMove(event);
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('pointermove', ['$event'])
|
||||
onPointerMove(event) {
|
||||
this.tooltipPosition = { x: event.clientX, y: event.clientY };
|
||||
this.cd.markForCheck();
|
||||
onPointerMove(event): void {
|
||||
if (this.epochSvgElement.nativeElement?.contains(event.target)) {
|
||||
this.tooltipPosition = { x: event.clientX, y: event.clientY };
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
onHover(event, rect): void {
|
||||
onHover(_, rect): void {
|
||||
this.hoverSection = rect;
|
||||
}
|
||||
|
||||
onBlur(event): void {
|
||||
onBlur(): void {
|
||||
this.hoverSection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges, OnDestr
|
||||
return;
|
||||
}
|
||||
const samples = [];
|
||||
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 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 maxBlockVSize = this.stateService.env.BLOCK_WEIGHT_UNITS / 4;
|
||||
const sampleInterval = maxBlockVSize / this.numSamples;
|
||||
let cumVSize = 0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<ng-container *ngIf="{ val: network$ | async } as network">
|
||||
<header>
|
||||
<header *ngIf="headerVisible">
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
|
||||
<a class="navbar-brand" [ngClass]="{'dual-logos': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
|
||||
<ng-template [ngIf]="subdomain">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Env, StateService } from '../../services/state.service';
|
||||
import { Observable, merge, of } from 'rxjs';
|
||||
import { LanguageService } from '../../services/language.service';
|
||||
@@ -11,6 +11,9 @@ import { NavigationService } from '../../services/navigation.service';
|
||||
styleUrls: ['./master-page.component.scss'],
|
||||
})
|
||||
export class MasterPageComponent implements OnInit {
|
||||
@Input() headerVisible = true;
|
||||
@Input() footerVisibleOverride: boolean | null = null;
|
||||
|
||||
env: Env;
|
||||
network$: Observable<string>;
|
||||
connectionState$: Observable<number>;
|
||||
@@ -38,10 +41,14 @@ export class MasterPageComponent implements OnInit {
|
||||
this.subdomain = this.enterpriseService.getSubdomain();
|
||||
this.navigationService.subnetPaths.subscribe((paths) => {
|
||||
this.networkPaths = paths;
|
||||
if (paths.mainnet.indexOf('docs') > -1) {
|
||||
this.footerVisible = false;
|
||||
if (this.footerVisibleOverride === null) {
|
||||
if (paths.mainnet.indexOf('docs') > -1) {
|
||||
this.footerVisible = false;
|
||||
} else {
|
||||
this.footerVisible = true;
|
||||
}
|
||||
} else {
|
||||
this.footerVisible = true;
|
||||
this.footerVisible = this.footerVisibleOverride;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -94,7 +94,6 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
||||
|
||||
updateBlock(delta: MempoolBlockDelta): void {
|
||||
const blockMined = (this.stateService.latestBlockHeight > this.lastBlockHeight);
|
||||
|
||||
if (this.blockIndex !== this.index) {
|
||||
const direction = (this.blockIndex == null || this.index < this.blockIndex) ? this.poolDirection : this.chainDirection;
|
||||
this.blockGraph.replace(delta.added, direction);
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div *ngIf="arrowVisible" id="arrow-up" [ngStyle]="{'right': rightPosition + 75 + 'px', transition: transition }"></div>
|
||||
<div *ngIf="arrowVisible" id="arrow-up" [ngStyle]="{'right': rightPosition + 75 + 'px', transition: transition }" [class.blink]="txPosition?.accelerated"></div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
||||
@@ -169,4 +169,34 @@
|
||||
transform: translate(calc(-0.2 * var(--block-size)), calc(1.1 * var(--block-size)));
|
||||
border-radius: 2px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.blink{
|
||||
width:400px;
|
||||
height:400px;
|
||||
border-bottom: 35px solid #FFF;
|
||||
animation: blink 0.2s infinite;
|
||||
}
|
||||
@keyframes blink{
|
||||
0% {
|
||||
border-bottom: 35px solid green;
|
||||
}
|
||||
50% {
|
||||
border-bottom: 35px solid yellow;
|
||||
}
|
||||
100% {
|
||||
border-bottom: 35px solid orange;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes blink{
|
||||
0% {
|
||||
border-bottom: 35px solid green;
|
||||
}
|
||||
50% {
|
||||
border-bottom: 35px solid yellow;
|
||||
}
|
||||
100% {
|
||||
border-bottom: 35px solid orange;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import { animate, style, transition, trigger } from '@angular/animations';
|
||||
export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() minimal: boolean = false;
|
||||
@Input() blockWidth: number = 125;
|
||||
@Input() containerWidth: number = null;
|
||||
@Input() count: number = null;
|
||||
@Input() spotlight: number = 0;
|
||||
@Input() getHref?: (index) => string = (index) => `/mempool-block/${index}`;
|
||||
|
||||
@@ -99,14 +99,20 @@
|
||||
</ng-template>
|
||||
<ng-template #estimationTmpl>
|
||||
<ng-template [ngIf]="this.mempoolPosition.block >= 7" [ngIfElse]="belowBlockLimit">
|
||||
<span i18n="transaction.eta.in-several-hours|Transaction ETA in several hours or more">In several hours (or more)</span>
|
||||
<span class="eta d-flex">
|
||||
<span i18n="transaction.eta.in-several-hours|Transaction ETA in several hours or more">In several hours (or more)</span>
|
||||
<span class="ml-2"></span><a *ngIf="stateService.env.OFFICIAL_MEMPOOL_SPACE && stateService.env.ACCELERATOR" [href]="'/services/accelerator/accelerate?txid=' + tx.txid" class="btn badge badge-primary accelerate ml-auto" i18n="transaction.accelerate|Accelerate button label">Accelerate</a>
|
||||
</span>
|
||||
</ng-template>
|
||||
<ng-template #belowBlockLimit>
|
||||
<ng-template [ngIf]="network === 'liquid' || network === 'liquidtestnet'" [ngIfElse]="timeEstimateDefault">
|
||||
<app-time kind="until" [time]="(60 * 1000 * this.mempoolPosition.block) + now" [fastRender]="false" [fixedRender]="true"></app-time>
|
||||
</ng-template>
|
||||
<ng-template #timeEstimateDefault>
|
||||
<app-time kind="until" *ngIf="(da$ | async) as da;" [time]="da.timeAvg * (this.mempoolPosition.block + 1) + now + da.timeOffset" [fastRender]="false" [fixedRender]="true"></app-time>
|
||||
<span class="d-flex">
|
||||
<app-time kind="until" *ngIf="(da$ | async) as da;" [time]="da.timeAvg * (this.mempoolPosition.block + 1) + now + da.timeOffset" [fastRender]="false" [fixedRender]="true"></app-time>
|
||||
<span class="ml-2"></span><a *ngIf="stateService.env.OFFICIAL_MEMPOOL_SPACE && stateService.env.ACCELERATOR" [href]="'/services/accelerator/accelerate?txid=' + tx.txid" class="btn badge badge-primary accelerate ml-auto" i18n="transaction.accelerate|Accelerate button label">Accelerate</a>
|
||||
</span>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
@@ -488,7 +494,8 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="cpfpInfo && hasEffectiveFeeRate">
|
||||
<td i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
|
||||
<td *ngIf="tx.acceleration" i18n="transaction.accelerated-fee-rate|Accelerated transaction fee rate">Accelerated fee rate</td>
|
||||
<td *ngIf="!tx.acceleration" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
|
||||
<td>
|
||||
<div class="effective-fee-container">
|
||||
<app-fee-rate [fee]="tx.effectiveFeePerVsize"></app-fee-rate>
|
||||
|
||||
@@ -216,4 +216,23 @@
|
||||
.alert-link {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.eta {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
@media (min-width: 850px) {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.accelerate {
|
||||
align-self: auto;
|
||||
margin-top: 3px;
|
||||
@media (min-width: 850px) {
|
||||
justify-self: start;
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
@@ -97,7 +97,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
private router: Router,
|
||||
private relativeUrlPipe: RelativeUrlPipe,
|
||||
private electrsApiService: ElectrsApiService,
|
||||
private stateService: StateService,
|
||||
public stateService: StateService,
|
||||
private cacheService: CacheService,
|
||||
private websocketService: WebsocketService,
|
||||
private audioService: AudioService,
|
||||
@@ -183,6 +183,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
} else {
|
||||
this.tx.effectiveFeePerVsize = cpfpInfo.effectiveFeePerVsize;
|
||||
}
|
||||
if (cpfpInfo.acceleration) {
|
||||
this.tx.acceleration = cpfpInfo.acceleration;
|
||||
}
|
||||
|
||||
this.cpfpInfo = cpfpInfo;
|
||||
this.hasEffectiveFeeRate = hasRelatives || (this.tx.effectiveFeePerVsize && (Math.abs(this.tx.effectiveFeePerVsize - this.tx.feePerVsize) > 0.01));
|
||||
|
||||
Reference in New Issue
Block a user