new page listing recent RBF events
This commit is contained in:
61
frontend/src/app/components/rbf-list/rbf-list.component.html
Normal file
61
frontend/src/app/components/rbf-list/rbf-list.component.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<div class="container-xl" style="min-height: 335px">
|
||||
<h1 class="float-left" i18n="page.rbf-replacements">RBF Replacements</h1>
|
||||
<div *ngIf="isLoading" class="spinner-border ml-3" role="status"></div>
|
||||
|
||||
<div class="mode-toggle float-right" *ngIf="fullRbfEnabled">
|
||||
<form class="formRadioGroup">
|
||||
<div class="btn-group btn-group-toggle" name="radioBasic">
|
||||
<label class="btn btn-primary btn-sm" [class.active]="!fullRbf">
|
||||
<input type="radio" [value]="'All'" fragment="" [routerLink]="[]"> All
|
||||
</label>
|
||||
<label class="btn btn-primary btn-sm" [class.active]="fullRbf">
|
||||
<input type="radio" [value]="'Full RBF'" fragment="fullrbf" [routerLink]="[]"> Full RBF
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="rbf-chains" style="min-height: 295px">
|
||||
<ng-container *ngIf="rbfChains$ | async as chains">
|
||||
<div *ngFor="let chain of chains" class="chain">
|
||||
<p class="info">
|
||||
<app-time kind="since" [time]="chain[chain.length - 1].time"></app-time>
|
||||
<span class="type">
|
||||
<span *ngIf="isMined(chain)" class="badge badge-success" i18n="transaction.rbf.mined">Mined</span>
|
||||
<span *ngIf="isFullRbf(chain)" class="badge badge-info" i18n="transaction.full-rbf">Full RBF</span>
|
||||
</span>
|
||||
</p>
|
||||
<div class="txids">
|
||||
<span class="txid">
|
||||
<a class="rbf-link" [routerLink]="['/tx/' | relativeUrl, chain[0].tx.txid]">
|
||||
<span class="d-inline">{{ chain[0].tx.txid | shortenString : 24 }}</span>
|
||||
</a>
|
||||
</span>
|
||||
<span class="arrow">
|
||||
<fa-icon [icon]="['fas', 'arrow-right']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
<span class="txid right">
|
||||
<a class="rbf-link" [routerLink]="['/tx/' | relativeUrl, chain[chain.length - 1].tx.txid]">
|
||||
<span class="d-inline">{{ chain[chain.length - 1].tx.txid | shortenString : 24 }}</span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="timeline-wrapper" [class.mined]="isMined(chain)">
|
||||
<app-rbf-timeline [replacements]="chain"></app-rbf-timeline>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="no-replacements" *ngIf="!chains?.length">
|
||||
<p i18n="rbf.no-replacements-yet">there are no replacements in the mempool yet!</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- <ngb-pagination class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''"
|
||||
[collectionSize]="blocksCount" [rotate]="true" [maxSize]="maxSize" [pageSize]="15" [(page)]="page"
|
||||
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
|
||||
</ngb-pagination> -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
51
frontend/src/app/components/rbf-list/rbf-list.component.scss
Normal file
51
frontend/src/app/components/rbf-list/rbf-list.component.scss
Normal file
@@ -0,0 +1,51 @@
|
||||
.spinner-border {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
margin-top: 13px;
|
||||
}
|
||||
|
||||
.rbf-chains {
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin: 0;
|
||||
|
||||
.type {
|
||||
.badge {
|
||||
margin-left: .5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chain {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.txids {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 2px;
|
||||
|
||||
.txid {
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
|
||||
&.right {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-wrapper.mined {
|
||||
border: solid 4px #1a9436;
|
||||
}
|
||||
|
||||
.no-replacements {
|
||||
margin: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
86
frontend/src/app/components/rbf-list/rbf-list.component.ts
Normal file
86
frontend/src/app/components/rbf-list/rbf-list.component.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BehaviorSubject, EMPTY, merge, Observable, Subscription } from 'rxjs';
|
||||
import { catchError, switchMap, tap } from 'rxjs/operators';
|
||||
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||
import { RbfInfo } from '../../interfaces/node-api.interface';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { StateService } from '../../services/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-rbf-list',
|
||||
templateUrl: './rbf-list.component.html',
|
||||
styleUrls: ['./rbf-list.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class RbfList implements OnInit, OnDestroy {
|
||||
rbfChains$: Observable<RbfInfo[][]>;
|
||||
fromChainSubject = new BehaviorSubject(null);
|
||||
urlFragmentSubscription: Subscription;
|
||||
fullRbfEnabled: boolean;
|
||||
fullRbf: boolean;
|
||||
isLoading = true;
|
||||
firstChainId: string;
|
||||
lastChainId: string;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private apiService: ApiService,
|
||||
public stateService: StateService,
|
||||
private websocketService: WebsocketService,
|
||||
) {
|
||||
this.fullRbfEnabled = stateService.env.FULL_RBF_ENABLED;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.urlFragmentSubscription = this.route.fragment.subscribe((fragment) => {
|
||||
this.fullRbf = (fragment === 'fullrbf');
|
||||
this.websocketService.startTrackRbf(this.fullRbf ? 'fullRbf' : 'all');
|
||||
this.fromChainSubject.next(this.firstChainId);
|
||||
});
|
||||
|
||||
this.rbfChains$ = merge(
|
||||
this.fromChainSubject.pipe(
|
||||
switchMap((fromChainId) => {
|
||||
return this.apiService.getRbfList$(this.fullRbf, fromChainId || undefined)
|
||||
}),
|
||||
catchError((e) => {
|
||||
return EMPTY;
|
||||
})
|
||||
),
|
||||
this.stateService.rbfLatest$
|
||||
)
|
||||
.pipe(
|
||||
tap((result: RbfInfo[][]) => {
|
||||
this.isLoading = false;
|
||||
if (result && result.length && result[0].length) {
|
||||
this.lastChainId = result[result.length - 1][0].tx.txid;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
toggleFullRbf(event) {
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
fragment: this.fullRbf ? null : 'fullrbf'
|
||||
});
|
||||
}
|
||||
|
||||
isFullRbf(chain: RbfInfo[]): boolean {
|
||||
return chain.slice(0, -1).some(entry => !entry.tx.rbf);
|
||||
}
|
||||
|
||||
isMined(chain: RbfInfo[]): boolean {
|
||||
return chain.some(entry => entry.mined);
|
||||
}
|
||||
|
||||
// pageChange(page: number) {
|
||||
// this.fromChainSubject.next(this.lastChainId);
|
||||
// }
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.websocketService.stopTrackRbf();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="rbf-timeline box">
|
||||
<div class="rbf-timeline box" [class.mined]="mined">
|
||||
<div class="timeline">
|
||||
<div class="intervals">
|
||||
<ng-container *ngFor="let replacement of replacements; let i = index;">
|
||||
@@ -15,7 +15,7 @@
|
||||
<div class="interval-spacer" *ngIf="i > 0">
|
||||
<div class="track"></div>
|
||||
</div>
|
||||
<div class="node" [class.selected]="txid === replacement.tx.txid">
|
||||
<div class="node" [class.selected]="txid === replacement.tx.txid" [class.mined]="replacement.mined">
|
||||
<div class="track"></div>
|
||||
<a class="shape-border" [class.rbf]="replacement.tx.rbf" [routerLink]="['/tx/' | relativeUrl, replacement.tx.txid]" [title]="replacement.tx.txid">
|
||||
<div class="shape"></div>
|
||||
|
||||
@@ -126,6 +126,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.mined {
|
||||
.shape-border {
|
||||
background: #1a9436;
|
||||
}
|
||||
}
|
||||
|
||||
.shape-border:hover {
|
||||
padding: 0px;
|
||||
.shape {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ApiService } from '../../services/api.service';
|
||||
export class RbfTimelineComponent implements OnInit, OnChanges {
|
||||
@Input() replacements: RbfInfo[];
|
||||
@Input() txid: string;
|
||||
mined: boolean;
|
||||
|
||||
dir: 'rtl' | 'ltr' = 'ltr';
|
||||
|
||||
@@ -27,10 +28,10 @@ export class RbfTimelineComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.mined = this.replacements.some(entry => entry.mined);
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
|
||||
this.mined = this.replacements.some(entry => entry.mined);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user