mirror of
https://github.com/mempool/mempool.git
synced 2024-11-19 09:52:14 +01:00
Merge pull request #5445 from mempool/mononaut/persistent-goggles
Persist mempool block visualization between pages
This commit is contained in:
commit
8c2d0e1d6c
@ -198,7 +198,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
}
|
}
|
||||||
|
|
||||||
// initialize the scene without any entry transition
|
// initialize the scene without any entry transition
|
||||||
setup(transactions: TransactionStripped[]): void {
|
setup(transactions: TransactionStripped[], sort: boolean = false): void {
|
||||||
const filtersAvailable = transactions.reduce((flagSet, tx) => flagSet || tx.flags > 0, false);
|
const filtersAvailable = transactions.reduce((flagSet, tx) => flagSet || tx.flags > 0, false);
|
||||||
if (filtersAvailable !== this.filtersAvailable) {
|
if (filtersAvailable !== this.filtersAvailable) {
|
||||||
this.setFilterFlags();
|
this.setFilterFlags();
|
||||||
@ -206,7 +206,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
this.filtersAvailable = filtersAvailable;
|
this.filtersAvailable = filtersAvailable;
|
||||||
if (this.scene) {
|
if (this.scene) {
|
||||||
this.clearUpdateQueue();
|
this.clearUpdateQueue();
|
||||||
this.scene.setup(transactions);
|
this.scene.setup(transactions, sort);
|
||||||
this.readyNextFrame = true;
|
this.readyNextFrame = true;
|
||||||
this.start();
|
this.start();
|
||||||
this.updateSearchHighlight();
|
this.updateSearchHighlight();
|
||||||
|
@ -88,16 +88,19 @@ export default class BlockScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set up the scene with an initial set of transactions, without any transition animation
|
// set up the scene with an initial set of transactions, without any transition animation
|
||||||
setup(txs: TransactionStripped[]) {
|
setup(txs: TransactionStripped[], sort: boolean = false) {
|
||||||
// clean up any old transactions
|
// clean up any old transactions
|
||||||
Object.values(this.txs).forEach(tx => {
|
Object.values(this.txs).forEach(tx => {
|
||||||
tx.destroy();
|
tx.destroy();
|
||||||
delete this.txs[tx.txid];
|
delete this.txs[tx.txid];
|
||||||
});
|
});
|
||||||
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
|
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
|
||||||
txs.forEach(tx => {
|
let txViews = txs.map(tx => new TxView(tx, this));
|
||||||
const txView = new TxView(tx, this);
|
if (sort) {
|
||||||
this.txs[tx.txid] = txView;
|
txViews = txViews.sort(feeRateDescending);
|
||||||
|
}
|
||||||
|
txViews.forEach(txView => {
|
||||||
|
this.txs[txView.txid] = txView;
|
||||||
this.place(txView);
|
this.place(txView);
|
||||||
this.saveGridToScreenPosition(txView);
|
this.saveGridToScreenPosition(txView);
|
||||||
this.applyTxUpdate(txView, {
|
this.applyTxUpdate(txView, {
|
||||||
|
@ -31,7 +31,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
|||||||
|
|
||||||
lastBlockHeight: number;
|
lastBlockHeight: number;
|
||||||
blockIndex: number;
|
blockIndex: number;
|
||||||
isLoading$ = new BehaviorSubject<boolean>(true);
|
isLoading$ = new BehaviorSubject<boolean>(false);
|
||||||
timeLtrSubscription: Subscription;
|
timeLtrSubscription: Subscription;
|
||||||
timeLtr: boolean;
|
timeLtr: boolean;
|
||||||
chainDirection: string = 'right';
|
chainDirection: string = 'right';
|
||||||
@ -95,6 +95,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.updateBlock({
|
this.updateBlock({
|
||||||
|
block: this.blockIndex,
|
||||||
removed,
|
removed,
|
||||||
changed,
|
changed,
|
||||||
added
|
added
|
||||||
@ -110,8 +111,11 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
|||||||
if (this.blockGraph) {
|
if (this.blockGraph) {
|
||||||
this.blockGraph.clear(changes.index.currentValue > changes.index.previousValue ? this.chainDirection : this.poolDirection);
|
this.blockGraph.clear(changes.index.currentValue > changes.index.previousValue ? this.chainDirection : this.poolDirection);
|
||||||
}
|
}
|
||||||
this.isLoading$.next(true);
|
if (!this.websocketService.startTrackMempoolBlock(changes.index.currentValue) && this.stateService.mempoolBlockState && this.stateService.mempoolBlockState.block === changes.index.currentValue) {
|
||||||
this.websocketService.startTrackMempoolBlock(changes.index.currentValue);
|
this.resumeBlock(Object.values(this.stateService.mempoolBlockState.transactions));
|
||||||
|
} else {
|
||||||
|
this.isLoading$.next(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +157,19 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
|||||||
this.isLoading$.next(false);
|
this.isLoading$.next(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resumeBlock(transactionsStripped: TransactionStripped[]): void {
|
||||||
|
if (this.blockGraph) {
|
||||||
|
this.firstLoad = false;
|
||||||
|
this.blockGraph.setup(transactionsStripped, true);
|
||||||
|
this.blockIndex = this.index;
|
||||||
|
this.isLoading$.next(false);
|
||||||
|
} else {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.resumeBlock(transactionsStripped);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onTxClick(event: { tx: TransactionStripped, keyModifier: boolean }): void {
|
onTxClick(event: { tx: TransactionStripped, keyModifier: boolean }): void {
|
||||||
const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.tx.txid}`);
|
const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.tx.txid}`);
|
||||||
if (!event.keyModifier) {
|
if (!event.keyModifier) {
|
||||||
|
@ -71,7 +71,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
this.mempoolBlockTransactions$ = this.stateService.liveMempoolBlockTransactions$.pipe(map(txMap => Object.values(txMap)));
|
this.mempoolBlockTransactions$ = this.stateService.liveMempoolBlockTransactions$.pipe(map(({transactions}) => Object.values(transactions)));
|
||||||
|
|
||||||
this.network$ = this.stateService.networkChanged$;
|
this.network$ = this.stateService.networkChanged$;
|
||||||
}
|
}
|
||||||
|
@ -72,11 +72,13 @@ export interface MempoolBlockWithTransactions extends MempoolBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MempoolBlockDelta {
|
export interface MempoolBlockDelta {
|
||||||
|
block: number;
|
||||||
added: TransactionStripped[];
|
added: TransactionStripped[];
|
||||||
removed: string[];
|
removed: string[];
|
||||||
changed: { txid: string, rate: number, flags: number, acc: boolean }[];
|
changed: { txid: string, rate: number, flags: number, acc: boolean }[];
|
||||||
}
|
}
|
||||||
export interface MempoolBlockState {
|
export interface MempoolBlockState {
|
||||||
|
block: number;
|
||||||
transactions: TransactionStripped[];
|
transactions: TransactionStripped[];
|
||||||
}
|
}
|
||||||
export type MempoolBlockUpdate = MempoolBlockDelta | MempoolBlockState;
|
export type MempoolBlockUpdate = MempoolBlockDelta | MempoolBlockState;
|
||||||
|
@ -5,7 +5,7 @@ import { AccelerationDelta, HealthCheckHost, IBackendInfo, MempoolBlock, Mempool
|
|||||||
import { Acceleration, AccelerationPosition, BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree, TransactionStripped } from '../interfaces/node-api.interface';
|
import { Acceleration, AccelerationPosition, BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree, TransactionStripped } from '../interfaces/node-api.interface';
|
||||||
import { Router, NavigationStart } from '@angular/router';
|
import { Router, NavigationStart } from '@angular/router';
|
||||||
import { isPlatformBrowser } from '@angular/common';
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
import { filter, map, scan, shareReplay } from 'rxjs/operators';
|
import { filter, map, scan, share, shareReplay } from 'rxjs/operators';
|
||||||
import { StorageService } from './storage.service';
|
import { StorageService } from './storage.service';
|
||||||
import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils';
|
import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils';
|
||||||
import { ActiveFilter } from '../shared/filters.utils';
|
import { ActiveFilter } from '../shared/filters.utils';
|
||||||
@ -131,6 +131,7 @@ export class StateService {
|
|||||||
latestBlockHeight = -1;
|
latestBlockHeight = -1;
|
||||||
blocks: BlockExtended[] = [];
|
blocks: BlockExtended[] = [];
|
||||||
mempoolSequence: number;
|
mempoolSequence: number;
|
||||||
|
mempoolBlockState: { block: number, transactions: { [txid: string]: TransactionStripped} };
|
||||||
|
|
||||||
backend$ = new BehaviorSubject<'esplora' | 'electrum' | 'none'>('esplora');
|
backend$ = new BehaviorSubject<'esplora' | 'electrum' | 'none'>('esplora');
|
||||||
networkChanged$ = new ReplaySubject<string>(1);
|
networkChanged$ = new ReplaySubject<string>(1);
|
||||||
@ -143,7 +144,7 @@ export class StateService {
|
|||||||
mempoolInfo$ = new ReplaySubject<MempoolInfo>(1);
|
mempoolInfo$ = new ReplaySubject<MempoolInfo>(1);
|
||||||
mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
|
mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
|
||||||
mempoolBlockUpdate$ = new Subject<MempoolBlockUpdate>();
|
mempoolBlockUpdate$ = new Subject<MempoolBlockUpdate>();
|
||||||
liveMempoolBlockTransactions$: Observable<{ [txid: string]: TransactionStripped}>;
|
liveMempoolBlockTransactions$: Observable<{ block: number, transactions: { [txid: string]: TransactionStripped} }>;
|
||||||
accelerations$ = new Subject<AccelerationDelta>();
|
accelerations$ = new Subject<AccelerationDelta>();
|
||||||
liveAccelerations$: Observable<Acceleration[]>;
|
liveAccelerations$: Observable<Acceleration[]>;
|
||||||
txConfirmed$ = new Subject<[string, BlockExtended]>();
|
txConfirmed$ = new Subject<[string, BlockExtended]>();
|
||||||
@ -231,29 +232,40 @@ export class StateService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.liveMempoolBlockTransactions$ = this.mempoolBlockUpdate$.pipe(scan((transactions: { [txid: string]: TransactionStripped }, change: MempoolBlockUpdate): { [txid: string]: TransactionStripped } => {
|
this.liveMempoolBlockTransactions$ = this.mempoolBlockUpdate$.pipe(scan((acc: { block: number, transactions: { [txid: string]: TransactionStripped } }, change: MempoolBlockUpdate): { block: number, transactions: { [txid: string]: TransactionStripped } } => {
|
||||||
if (isMempoolState(change)) {
|
if (isMempoolState(change)) {
|
||||||
const txMap = {};
|
const txMap = {};
|
||||||
change.transactions.forEach(tx => {
|
change.transactions.forEach(tx => {
|
||||||
txMap[tx.txid] = tx;
|
txMap[tx.txid] = tx;
|
||||||
});
|
});
|
||||||
return txMap;
|
this.mempoolBlockState = {
|
||||||
|
block: change.block,
|
||||||
|
transactions: txMap
|
||||||
|
};
|
||||||
|
return this.mempoolBlockState;
|
||||||
} else {
|
} else {
|
||||||
change.added.forEach(tx => {
|
change.added.forEach(tx => {
|
||||||
transactions[tx.txid] = tx;
|
acc.transactions[tx.txid] = tx;
|
||||||
});
|
});
|
||||||
change.removed.forEach(txid => {
|
change.removed.forEach(txid => {
|
||||||
delete transactions[txid];
|
delete acc.transactions[txid];
|
||||||
});
|
});
|
||||||
change.changed.forEach(tx => {
|
change.changed.forEach(tx => {
|
||||||
if (transactions[tx.txid]) {
|
if (acc.transactions[tx.txid]) {
|
||||||
transactions[tx.txid].rate = tx.rate;
|
acc.transactions[tx.txid].rate = tx.rate;
|
||||||
transactions[tx.txid].acc = tx.acc;
|
acc.transactions[tx.txid].acc = tx.acc;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return transactions;
|
this.mempoolBlockState = {
|
||||||
|
block: change.block,
|
||||||
|
transactions: acc.transactions
|
||||||
|
};
|
||||||
|
return this.mempoolBlockState;
|
||||||
}
|
}
|
||||||
}, {}));
|
}, {}),
|
||||||
|
share()
|
||||||
|
);
|
||||||
|
this.liveMempoolBlockTransactions$.subscribe();
|
||||||
|
|
||||||
// Emits the full list of pending accelerations each time it changes
|
// Emits the full list of pending accelerations each time it changes
|
||||||
this.liveAccelerations$ = this.accelerations$.pipe(
|
this.liveAccelerations$ = this.accelerations$.pipe(
|
||||||
|
@ -35,6 +35,7 @@ export class WebsocketService {
|
|||||||
private isTrackingAddresses: string[] | false = false;
|
private isTrackingAddresses: string[] | false = false;
|
||||||
private isTrackingAccelerations: boolean = false;
|
private isTrackingAccelerations: boolean = false;
|
||||||
private trackingMempoolBlock: number;
|
private trackingMempoolBlock: number;
|
||||||
|
private stoppingTrackMempoolBlock: any | null = null;
|
||||||
private latestGitCommit = '';
|
private latestGitCommit = '';
|
||||||
private onlineCheckTimeout: number;
|
private onlineCheckTimeout: number;
|
||||||
private onlineCheckTimeoutTwo: number;
|
private onlineCheckTimeoutTwo: number;
|
||||||
@ -203,19 +204,31 @@ export class WebsocketService {
|
|||||||
this.websocketSubject.next({ 'track-asset': 'stop' });
|
this.websocketSubject.next({ 'track-asset': 'stop' });
|
||||||
}
|
}
|
||||||
|
|
||||||
startTrackMempoolBlock(block: number, force: boolean = false) {
|
startTrackMempoolBlock(block: number, force: boolean = false): boolean {
|
||||||
|
if (this.stoppingTrackMempoolBlock) {
|
||||||
|
clearTimeout(this.stoppingTrackMempoolBlock);
|
||||||
|
}
|
||||||
// skip duplicate tracking requests
|
// skip duplicate tracking requests
|
||||||
if (force || this.trackingMempoolBlock !== block) {
|
if (force || this.trackingMempoolBlock !== block) {
|
||||||
this.websocketSubject.next({ 'track-mempool-block': block });
|
this.websocketSubject.next({ 'track-mempool-block': block });
|
||||||
this.isTrackingMempoolBlock = true;
|
this.isTrackingMempoolBlock = true;
|
||||||
this.trackingMempoolBlock = block;
|
this.trackingMempoolBlock = block;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
stopTrackMempoolBlock() {
|
stopTrackMempoolBlock(): void {
|
||||||
this.websocketSubject.next({ 'track-mempool-block': -1 });
|
if (this.stoppingTrackMempoolBlock) {
|
||||||
|
clearTimeout(this.stoppingTrackMempoolBlock);
|
||||||
|
}
|
||||||
this.isTrackingMempoolBlock = false;
|
this.isTrackingMempoolBlock = false;
|
||||||
this.trackingMempoolBlock = null;
|
this.stoppingTrackMempoolBlock = setTimeout(() => {
|
||||||
|
this.stoppingTrackMempoolBlock = null;
|
||||||
|
this.websocketSubject.next({ 'track-mempool-block': -1 });
|
||||||
|
this.trackingMempoolBlock = null;
|
||||||
|
this.stateService.mempoolBlockState = null;
|
||||||
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
startTrackRbf(mode: 'all' | 'fullRbf') {
|
startTrackRbf(mode: 'all' | 'fullRbf') {
|
||||||
@ -424,6 +437,7 @@ export class WebsocketService {
|
|||||||
if (response['projected-block-transactions'].blockTransactions) {
|
if (response['projected-block-transactions'].blockTransactions) {
|
||||||
this.stateService.mempoolSequence = response['projected-block-transactions'].sequence;
|
this.stateService.mempoolSequence = response['projected-block-transactions'].sequence;
|
||||||
this.stateService.mempoolBlockUpdate$.next({
|
this.stateService.mempoolBlockUpdate$.next({
|
||||||
|
block: this.trackingMempoolBlock,
|
||||||
transactions: response['projected-block-transactions'].blockTransactions.map(uncompressTx),
|
transactions: response['projected-block-transactions'].blockTransactions.map(uncompressTx),
|
||||||
});
|
});
|
||||||
} else if (response['projected-block-transactions'].delta) {
|
} else if (response['projected-block-transactions'].delta) {
|
||||||
@ -432,7 +446,7 @@ export class WebsocketService {
|
|||||||
this.startTrackMempoolBlock(this.trackingMempoolBlock, true);
|
this.startTrackMempoolBlock(this.trackingMempoolBlock, true);
|
||||||
} else {
|
} else {
|
||||||
this.stateService.mempoolSequence = response['projected-block-transactions'].sequence;
|
this.stateService.mempoolSequence = response['projected-block-transactions'].sequence;
|
||||||
this.stateService.mempoolBlockUpdate$.next(uncompressDeltaChange(response['projected-block-transactions'].delta));
|
this.stateService.mempoolBlockUpdate$.next(uncompressDeltaChange(this.trackingMempoolBlock, response['projected-block-transactions'].delta));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,8 +170,9 @@ export function uncompressTx(tx: TransactionCompressed): TransactionStripped {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uncompressDeltaChange(delta: MempoolBlockDeltaCompressed): MempoolBlockDelta {
|
export function uncompressDeltaChange(block: number, delta: MempoolBlockDeltaCompressed): MempoolBlockDelta {
|
||||||
return {
|
return {
|
||||||
|
block,
|
||||||
added: delta.added.map(uncompressTx),
|
added: delta.added.map(uncompressTx),
|
||||||
removed: delta.removed,
|
removed: delta.removed,
|
||||||
changed: delta.changed.map(tx => ({
|
changed: delta.changed.map(tx => ({
|
||||||
|
Loading…
Reference in New Issue
Block a user