eight blocks
This commit is contained in:
parent
108d1762d6
commit
ec8fc53dcb
@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
|
|||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
import { AppPreloadingStrategy } from './app.preloading-strategy'
|
import { AppPreloadingStrategy } from './app.preloading-strategy'
|
||||||
import { BlockViewComponent } from './components/block-view/block-view.component';
|
import { BlockViewComponent } from './components/block-view/block-view.component';
|
||||||
|
import { EightBlocksComponent } from './components/eight-blocks/eight-blocks.component';
|
||||||
import { MempoolBlockViewComponent } from './components/mempool-block-view/mempool-block-view.component';
|
import { MempoolBlockViewComponent } from './components/mempool-block-view/mempool-block-view.component';
|
||||||
import { ClockComponent } from './components/clock/clock.component';
|
import { ClockComponent } from './components/clock/clock.component';
|
||||||
import { StatusViewComponent } from './components/status-view/status-view.component';
|
import { StatusViewComponent } from './components/status-view/status-view.component';
|
||||||
@ -124,6 +125,10 @@ let routes: Routes = [
|
|||||||
path: 'view/mempool-block/:index',
|
path: 'view/mempool-block/:index',
|
||||||
component: MempoolBlockViewComponent,
|
component: MempoolBlockViewComponent,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'eight-blocks',
|
||||||
|
component: EightBlocksComponent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'status',
|
path: 'status',
|
||||||
data: { networks: ['bitcoin', 'liquid'] },
|
data: { networks: ['bitcoin', 'liquid'] },
|
||||||
|
@ -20,6 +20,8 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
@Input() blockLimit: number;
|
@Input() blockLimit: number;
|
||||||
@Input() orientation = 'left';
|
@Input() orientation = 'left';
|
||||||
@Input() flip = true;
|
@Input() flip = true;
|
||||||
|
@Input() animationDuration: number = 1000;
|
||||||
|
@Input() animationOffset: number | null = null;
|
||||||
@Input() disableSpinner = false;
|
@Input() disableSpinner = false;
|
||||||
@Input() mirrorTxid: string | void;
|
@Input() mirrorTxid: string | void;
|
||||||
@Input() unavailable: boolean = false;
|
@Input() unavailable: boolean = false;
|
||||||
@ -141,9 +143,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
replace(transactions: TransactionStripped[], direction: string, sort: boolean = true): void {
|
replace(transactions: TransactionStripped[], direction: string, sort: boolean = true, startTime?: number): void {
|
||||||
if (this.scene) {
|
if (this.scene) {
|
||||||
this.scene.replace(transactions || [], direction, sort);
|
this.scene.replace(transactions || [], direction, sort, startTime);
|
||||||
this.start();
|
this.start();
|
||||||
this.updateSearchHighlight();
|
this.updateSearchHighlight();
|
||||||
}
|
}
|
||||||
@ -226,7 +228,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
} else {
|
} else {
|
||||||
this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution,
|
this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution,
|
||||||
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray,
|
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray,
|
||||||
highlighting: this.auditHighlighting });
|
highlighting: this.auditHighlighting, animationDuration: this.animationDuration, animationOffset: this.animationOffset });
|
||||||
this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,9 @@ export default class BlockScene {
|
|||||||
txs: { [key: string]: TxView };
|
txs: { [key: string]: TxView };
|
||||||
orientation: string;
|
orientation: string;
|
||||||
flip: boolean;
|
flip: boolean;
|
||||||
|
animationDuration: number = 1000;
|
||||||
|
configAnimationOffset: number | null;
|
||||||
|
animationOffset: number;
|
||||||
highlightingEnabled: boolean;
|
highlightingEnabled: boolean;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
@ -23,11 +26,11 @@ export default class BlockScene {
|
|||||||
animateUntil = 0;
|
animateUntil = 0;
|
||||||
dirty: boolean;
|
dirty: boolean;
|
||||||
|
|
||||||
constructor({ width, height, resolution, blockLimit, orientation, flip, vertexArray, highlighting }:
|
constructor({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting }:
|
||||||
{ width: number, height: number, resolution: number, blockLimit: number,
|
{ width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number,
|
||||||
orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean }
|
orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean }
|
||||||
) {
|
) {
|
||||||
this.init({ width, height, resolution, blockLimit, orientation, flip, vertexArray, highlighting });
|
this.init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting });
|
||||||
}
|
}
|
||||||
|
|
||||||
resize({ width = this.width, height = this.height, animate = true }: { width?: number, height?: number, animate: boolean }): void {
|
resize({ width = this.width, height = this.height, animate = true }: { width?: number, height?: number, animate: boolean }): void {
|
||||||
@ -36,6 +39,7 @@ export default class BlockScene {
|
|||||||
this.gridSize = this.width / this.gridWidth;
|
this.gridSize = this.width / this.gridWidth;
|
||||||
this.unitPadding = Math.max(1, Math.floor(this.gridSize / 5));
|
this.unitPadding = Math.max(1, Math.floor(this.gridSize / 5));
|
||||||
this.unitWidth = this.gridSize - (this.unitPadding * 2);
|
this.unitWidth = this.gridSize - (this.unitPadding * 2);
|
||||||
|
this.animationOffset = this.configAnimationOffset == null ? (this.width * 1.4) : this.configAnimationOffset;
|
||||||
|
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
if (this.initialised && this.scene) {
|
if (this.initialised && this.scene) {
|
||||||
@ -90,8 +94,8 @@ export default class BlockScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Animate new block entering scene
|
// Animate new block entering scene
|
||||||
enter(txs: TransactionStripped[], direction) {
|
enter(txs: TransactionStripped[], direction, startTime?: number) {
|
||||||
this.replace(txs, direction);
|
this.replace(txs, direction, false, startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animate block leaving scene
|
// Animate block leaving scene
|
||||||
@ -108,8 +112,7 @@ export default class BlockScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset layout and replace with new set of transactions
|
// Reset layout and replace with new set of transactions
|
||||||
replace(txs: TransactionStripped[], direction: string = 'left', sort: boolean = true): void {
|
replace(txs: TransactionStripped[], direction: string = 'left', sort: boolean = true, startTime: number = performance.now()): void {
|
||||||
const startTime = performance.now();
|
|
||||||
const nextIds = {};
|
const nextIds = {};
|
||||||
const remove = [];
|
const remove = [];
|
||||||
txs.forEach(tx => {
|
txs.forEach(tx => {
|
||||||
@ -133,7 +136,7 @@ export default class BlockScene {
|
|||||||
removed.forEach(tx => {
|
removed.forEach(tx => {
|
||||||
tx.destroy();
|
tx.destroy();
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, (startTime - performance.now()) + this.animationDuration + 1000);
|
||||||
|
|
||||||
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
|
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
|
||||||
|
|
||||||
@ -147,7 +150,7 @@ export default class BlockScene {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateAll(startTime, 200, direction);
|
this.updateAll(startTime, 50, direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined, acc: boolean | 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 {
|
||||||
@ -214,10 +217,13 @@ export default class BlockScene {
|
|||||||
this.animateUntil = Math.max(this.animateUntil, tx.setHighlight(value));
|
this.animateUntil = Math.max(this.animateUntil, tx.setHighlight(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
private init({ width, height, resolution, blockLimit, orientation, flip, vertexArray, highlighting }:
|
private init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting }:
|
||||||
{ width: number, height: number, resolution: number, blockLimit: number,
|
{ width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number,
|
||||||
orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean }
|
orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean }
|
||||||
): void {
|
): void {
|
||||||
|
this.animationDuration = animationDuration || 1000;
|
||||||
|
this.configAnimationOffset = animationOffset;
|
||||||
|
this.animationOffset = this.configAnimationOffset == null ? (this.width * 1.4) : this.configAnimationOffset;
|
||||||
this.orientation = orientation;
|
this.orientation = orientation;
|
||||||
this.flip = flip;
|
this.flip = flip;
|
||||||
this.vertexArray = vertexArray;
|
this.vertexArray = vertexArray;
|
||||||
@ -261,8 +267,8 @@ export default class BlockScene {
|
|||||||
this.applyTxUpdate(tx, {
|
this.applyTxUpdate(tx, {
|
||||||
display: {
|
display: {
|
||||||
position: {
|
position: {
|
||||||
x: tx.screenPosition.x + (direction === 'right' ? -this.width : (direction === 'left' ? this.width : 0)) * 1.4,
|
x: tx.screenPosition.x + (direction === 'right' ? -this.width - this.animationOffset : (direction === 'left' ? this.width + this.animationOffset : 0)),
|
||||||
y: tx.screenPosition.y + (direction === 'up' ? -this.height : (direction === 'down' ? this.height : 0)) * 1.4,
|
y: tx.screenPosition.y + (direction === 'up' ? -this.height - this.animationOffset : (direction === 'down' ? this.height + this.animationOffset : 0)),
|
||||||
s: tx.screenPosition.s
|
s: tx.screenPosition.s
|
||||||
},
|
},
|
||||||
color: txColor,
|
color: txColor,
|
||||||
@ -275,7 +281,7 @@ export default class BlockScene {
|
|||||||
position: tx.screenPosition,
|
position: tx.screenPosition,
|
||||||
color: txColor
|
color: txColor
|
||||||
},
|
},
|
||||||
duration: animate ? 1000 : 1,
|
duration: animate ? this.animationDuration : 1,
|
||||||
start: startTime,
|
start: startTime,
|
||||||
delay: animate ? delay : 0,
|
delay: animate ? delay : 0,
|
||||||
});
|
});
|
||||||
@ -284,8 +290,8 @@ export default class BlockScene {
|
|||||||
display: {
|
display: {
|
||||||
position: tx.screenPosition
|
position: tx.screenPosition
|
||||||
},
|
},
|
||||||
duration: animate ? 1000 : 0,
|
duration: animate ? this.animationDuration : 0,
|
||||||
minDuration: animate ? 500 : 0,
|
minDuration: animate ? (this.animationDuration / 2) : 0,
|
||||||
start: startTime,
|
start: startTime,
|
||||||
delay: animate ? delay : 0,
|
delay: animate ? delay : 0,
|
||||||
adjust: animate
|
adjust: animate
|
||||||
@ -322,11 +328,11 @@ export default class BlockScene {
|
|||||||
this.applyTxUpdate(tx, {
|
this.applyTxUpdate(tx, {
|
||||||
display: {
|
display: {
|
||||||
position: {
|
position: {
|
||||||
x: tx.screenPosition.x + (direction === 'right' ? this.width : (direction === 'left' ? -this.width : 0)) * 1.4,
|
x: tx.screenPosition.x + (direction === 'right' ? this.width + this.animationOffset : (direction === 'left' ? -this.width - this.animationOffset : 0)),
|
||||||
y: tx.screenPosition.y + (direction === 'up' ? this.height : (direction === 'down' ? -this.height : 0)) * 1.4,
|
y: tx.screenPosition.y + (direction === 'up' ? this.height + this.animationOffset : (direction === 'down' ? -this.height - this.animationOffset : 0)),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
duration: 1000,
|
duration: this.animationDuration,
|
||||||
start: startTime,
|
start: startTime,
|
||||||
delay: 50
|
delay: 50
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
<div class="blocks" [class.wrap]="wrapBlocks">
|
||||||
|
<ng-container *ngFor="let i of [0,1,2,3,4,5,6,7]">
|
||||||
|
<div class="block-wrapper" [style]="wrapperStyle">
|
||||||
|
<div class="block-container" [style]="containerStyle">
|
||||||
|
<app-block-overview-graph
|
||||||
|
#blockGraph
|
||||||
|
[isLoading]="false"
|
||||||
|
[resolution]="resolution"
|
||||||
|
[blockLimit]="stateService.blockVSize"
|
||||||
|
[orientation]="'top'"
|
||||||
|
[flip]="false"
|
||||||
|
[animationDuration]="animationDuration"
|
||||||
|
[animationOffset]="animationOffset"
|
||||||
|
[disableSpinner]="true"
|
||||||
|
(txClickEvent)="onTxClick($event)"
|
||||||
|
></app-block-overview-graph>
|
||||||
|
<div *ngIf="showInfo && blockInfo[i]" class="info" @infoChange>
|
||||||
|
<h1>{{ blockInfo[i].height }}</h1>
|
||||||
|
<p class="hash">{{ blockInfo[i].hash }}</p>
|
||||||
|
<h2>{{ blockInfo[i].time }}</h2>
|
||||||
|
<h2>{{ blockInfo[i].count }}</h2>
|
||||||
|
<h2>{{ blockInfo[i].size }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
@ -0,0 +1,50 @@
|
|||||||
|
.blocks {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: start;
|
||||||
|
align-items: start;
|
||||||
|
align-content: start;
|
||||||
|
|
||||||
|
&.wrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-wrapper {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
--block-width: 1080px;
|
||||||
|
|
||||||
|
.info {
|
||||||
|
position: absolute;
|
||||||
|
left: 10%;
|
||||||
|
top: 10%;
|
||||||
|
right: 10%;
|
||||||
|
bottom: 10%;
|
||||||
|
height: 80%;
|
||||||
|
width: 80%;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: calc(var(--block-width) * 0.04);
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 4em;
|
||||||
|
margin-bottom: 0.1em;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 2.5em;
|
||||||
|
margin-bottom: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hash {
|
||||||
|
font-family: monospace;
|
||||||
|
word-wrap: break-word;
|
||||||
|
font-size: 1em;
|
||||||
|
margin-bottom: 0.1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,244 @@
|
|||||||
|
import { Component, OnInit, OnDestroy, ViewChildren, QueryList } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { catchError, startWith } from 'rxjs/operators';
|
||||||
|
import { Subject, Subscription, of } from 'rxjs';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
|
import { BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
|
import { ApiService } from '../../services/api.service';
|
||||||
|
import { BlockOverviewGraphComponent } from '../block-overview-graph/block-overview-graph.component';
|
||||||
|
import { detectWebGL } from '../../shared/graphs.utils';
|
||||||
|
import { animate, style, transition, trigger } from '@angular/animations';
|
||||||
|
import { BytesPipe } from '../../shared/pipes/bytes-pipe/bytes.pipe';
|
||||||
|
|
||||||
|
function bestFitResolution(min, max, n): number {
|
||||||
|
const target = (min + max) / 2;
|
||||||
|
let bestScore = Infinity;
|
||||||
|
let best = null;
|
||||||
|
for (let i = min; i <= max; i++) {
|
||||||
|
const remainder = (n % i);
|
||||||
|
if (remainder < bestScore || (remainder === bestScore && (Math.abs(i - target) < Math.abs(best - target)))) {
|
||||||
|
bestScore = remainder;
|
||||||
|
best = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-eight-blocks',
|
||||||
|
templateUrl: './eight-blocks.component.html',
|
||||||
|
styleUrls: ['./eight-blocks.component.scss'],
|
||||||
|
animations: [
|
||||||
|
trigger('infoChange', [
|
||||||
|
transition(':enter', [
|
||||||
|
style({ opacity: 0 }),
|
||||||
|
animate('1000ms', style({ opacity: 1 })),
|
||||||
|
]),
|
||||||
|
transition(':leave', [
|
||||||
|
animate('1000ms 500ms', style({ opacity: 0 }))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class EightBlocksComponent implements OnInit, OnDestroy {
|
||||||
|
network = '';
|
||||||
|
latestBlocks: BlockExtended[] = [];
|
||||||
|
isLoadingTransactions = true;
|
||||||
|
strippedTransactions: { [height: number]: TransactionStripped[] } = {};
|
||||||
|
webGlEnabled = true;
|
||||||
|
hoverTx: string | null = null;
|
||||||
|
|
||||||
|
blocksSubscription: Subscription;
|
||||||
|
cacheBlocksSubscription: Subscription;
|
||||||
|
networkChangedSubscription: Subscription;
|
||||||
|
queryParamsSubscription: Subscription;
|
||||||
|
graphChangeSubscription: Subscription;
|
||||||
|
|
||||||
|
autofit: boolean = false;
|
||||||
|
padding: number = 0;
|
||||||
|
wrapBlocks: boolean = false;
|
||||||
|
blockWidth: number = 1080;
|
||||||
|
animationDuration: number = 2000;
|
||||||
|
animationOffset: number = 0;
|
||||||
|
stagger: number = 0;
|
||||||
|
testing: boolean = true;
|
||||||
|
testHeight: number = 800000;
|
||||||
|
testShiftTimeout: number;
|
||||||
|
|
||||||
|
showInfo: boolean = true;
|
||||||
|
blockInfo: { [key: string]: string}[] = [];
|
||||||
|
|
||||||
|
wrapperStyle = {
|
||||||
|
'--block-width': '1080px',
|
||||||
|
width: '1080px',
|
||||||
|
maxWidth: '1080px',
|
||||||
|
padding: '',
|
||||||
|
};
|
||||||
|
containerStyle = {};
|
||||||
|
resolution: number = 86;
|
||||||
|
|
||||||
|
@ViewChildren('blockGraph') blockGraphs: QueryList<BlockOverviewGraphComponent>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
public stateService: StateService,
|
||||||
|
private websocketService: WebsocketService,
|
||||||
|
private apiService: ApiService,
|
||||||
|
private bytesPipe: BytesPipe,
|
||||||
|
) {
|
||||||
|
this.webGlEnabled = detectWebGL();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.websocketService.want(['blocks']);
|
||||||
|
this.network = this.stateService.network;
|
||||||
|
|
||||||
|
this.blocksSubscription = this.stateService.blocks$
|
||||||
|
.subscribe((blocks) => {
|
||||||
|
this.handleNewBlock(blocks);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
||||||
|
this.autofit = params.autofit === 'true';
|
||||||
|
this.padding = Number.isInteger(Number(params.padding)) ? Number(params.padding) : 0;
|
||||||
|
this.blockWidth = Number.isInteger(Number(params.blockWidth)) ? Number(params.blockWidth) : 1080;
|
||||||
|
this.wrapBlocks = params.wrap === 'true';
|
||||||
|
this.stagger = Number.isInteger(Number(params.stagger)) ? Number(params.stagger) : 0;
|
||||||
|
this.animationDuration = Number.isInteger(Number(params.animationDuration)) ? Number(params.animationDuration) : 2000;
|
||||||
|
this.animationOffset = this.padding * 2;
|
||||||
|
|
||||||
|
if (this.autofit) {
|
||||||
|
this.resolution = bestFitResolution(76, 96, this.blockWidth - this.padding * 2);
|
||||||
|
} else {
|
||||||
|
this.resolution = 86;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.wrapperStyle = {
|
||||||
|
'--block-width': this.blockWidth + 'px',
|
||||||
|
width: this.blockWidth + 'px',
|
||||||
|
maxWidth: this.blockWidth + 'px',
|
||||||
|
padding: (this.padding || 0) +'px 0px',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params.test === 'true') {
|
||||||
|
this.blocksSubscription.unsubscribe();
|
||||||
|
this.blocksSubscription = (new Subject<BlockExtended[]>()).subscribe((blocks) => {
|
||||||
|
this.handleNewBlock(blocks);
|
||||||
|
});
|
||||||
|
this.shiftTestBlocks();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setupBlockGraphs();
|
||||||
|
|
||||||
|
this.networkChangedSubscription = this.stateService.networkChanged$
|
||||||
|
.subscribe((network) => this.network = network);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
this.graphChangeSubscription = this.blockGraphs.changes.pipe(startWith(null)).subscribe(() => {
|
||||||
|
this.setupBlockGraphs();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.stateService.markBlock$.next({});
|
||||||
|
this.blocksSubscription?.unsubscribe();
|
||||||
|
this.cacheBlocksSubscription?.unsubscribe();
|
||||||
|
this.networkChangedSubscription?.unsubscribe();
|
||||||
|
this.queryParamsSubscription?.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
shiftTestBlocks(): void {
|
||||||
|
const sub = this.apiService.getBlocks$(this.testHeight).subscribe(result => {
|
||||||
|
sub.unsubscribe();
|
||||||
|
this.handleNewBlock(result);
|
||||||
|
this.testHeight++;
|
||||||
|
clearTimeout(this.testShiftTimeout);
|
||||||
|
this.testShiftTimeout = window.setTimeout(() => { this.shiftTestBlocks(); }, 10000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleNewBlock(blocks: BlockExtended[]): Promise<void> {
|
||||||
|
const readyPromises: Promise<TransactionStripped[]>[] = [];
|
||||||
|
const previousBlocks = this.latestBlocks;
|
||||||
|
const newHeights = {};
|
||||||
|
this.latestBlocks = blocks;
|
||||||
|
for (const block of blocks) {
|
||||||
|
newHeights[block.height] = true;
|
||||||
|
if (!this.strippedTransactions[block.height]) {
|
||||||
|
readyPromises.push(new Promise((resolve) => {
|
||||||
|
const subscription = this.apiService.getStrippedBlockTransactions$(block.id).pipe(
|
||||||
|
catchError(() => {
|
||||||
|
return of([]);
|
||||||
|
}),
|
||||||
|
).subscribe((transactions) => {
|
||||||
|
this.strippedTransactions[block.height] = transactions;
|
||||||
|
subscription.unsubscribe();
|
||||||
|
resolve(transactions);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.allSettled(readyPromises);
|
||||||
|
this.updateBlockGraphs(blocks);
|
||||||
|
|
||||||
|
// free up old transactions
|
||||||
|
previousBlocks.forEach(block => {
|
||||||
|
if (!newHeights[block.height]) {
|
||||||
|
delete this.strippedTransactions[block.height];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBlockGraphs(blocks): void {
|
||||||
|
const startTime = performance.now() + 1000 - (this.stagger < 0 ? this.stagger * 8 : 0);
|
||||||
|
if (this.blockGraphs) {
|
||||||
|
this.blockGraphs.forEach((graph, index) => {
|
||||||
|
graph.replace(this.strippedTransactions[blocks?.[index]?.height] || [], 'right', false, startTime + (this.stagger * index));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.showInfo = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.blockInfo = blocks.map(block => {
|
||||||
|
return {
|
||||||
|
height: `${block.height}`,
|
||||||
|
hash: block.id,
|
||||||
|
time: (new Date(block.timestamp * 1000)).toLocaleTimeString(),
|
||||||
|
count: `${block.tx_count} txs`,
|
||||||
|
size: `${this.bytesPipe.transform(block.size, 2, 'B', 'MB', true)}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.showInfo = true;
|
||||||
|
}, 1600); // Should match the animation time.
|
||||||
|
}
|
||||||
|
|
||||||
|
setupBlockGraphs(): void {
|
||||||
|
if (this.blockGraphs) {
|
||||||
|
this.blockGraphs.forEach((graph, index) => {
|
||||||
|
graph.destroy();
|
||||||
|
graph.setup(this.strippedTransactions[this.latestBlocks?.[index]?.height] || []);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTxClick(event: { tx: TransactionStripped, keyModifier: boolean }): void {
|
||||||
|
const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.tx.txid}`);
|
||||||
|
if (!event.keyModifier) {
|
||||||
|
this.router.navigate([url]);
|
||||||
|
} else {
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTxHover(txid: string): void {
|
||||||
|
if (txid && txid.length) {
|
||||||
|
this.hoverTx = txid;
|
||||||
|
} else {
|
||||||
|
this.hoverTx = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -237,7 +237,7 @@
|
|||||||
<div class="card-text" *ngIf="(isLoadingWebSocket$ | async) === false && mempoolInfoData.value; else loadingbig">
|
<div class="card-text" *ngIf="(isLoadingWebSocket$ | async) === false && mempoolInfoData.value; else loadingbig">
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
<div class="progress-bar {{ mempoolInfoData.value.mempoolSizeProgress }}" role="progressbar" [ngStyle]="{'width': (mempoolInfoData.value.memPoolInfo.usage / mempoolInfoData.value.memPoolInfo.maxmempool * 100) + '%' }"> </div>
|
<div class="progress-bar {{ mempoolInfoData.value.mempoolSizeProgress }}" role="progressbar" [ngStyle]="{'width': (mempoolInfoData.value.memPoolInfo.usage / mempoolInfoData.value.memPoolInfo.maxmempool * 100) + '%' }"> </div>
|
||||||
<div class="progress-text">‎<span [innerHTML]="mempoolInfoData.value.memPoolInfo.usage | bytes : 2 : 'B' : null : 3"></span> / <span [innerHTML]="mempoolInfoData.value.memPoolInfo.maxmempool | bytes"></span></div>
|
<div class="progress-text">‎<span [innerHTML]="mempoolInfoData.value.memPoolInfo.usage | bytes : 2 : 'B' : null : false : 3"></span> / <span [innerHTML]="mempoolInfoData.value.memPoolInfo.maxmempool | bytes"></span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,7 +17,7 @@ export class BytesPipe implements PipeTransform {
|
|||||||
'TB': {max: Number.MAX_SAFE_INTEGER, prev: 'GB'}
|
'TB': {max: Number.MAX_SAFE_INTEGER, prev: 'GB'}
|
||||||
};
|
};
|
||||||
|
|
||||||
transform(input: any, decimal: number = 0, from: ByteUnit = 'B', to?: ByteUnit, sigfigs?: number): any {
|
transform(input: any, decimal: number = 0, from: ByteUnit = 'B', to?: ByteUnit, plaintext = false, sigfigs?: number): any {
|
||||||
|
|
||||||
if (!(isNumberFinite(input) &&
|
if (!(isNumberFinite(input) &&
|
||||||
isNumberFinite(decimal) &&
|
isNumberFinite(decimal) &&
|
||||||
@ -42,7 +42,7 @@ export class BytesPipe implements PipeTransform {
|
|||||||
|
|
||||||
const result = numberFormat(BytesPipe.calculateResult(format, bytes));
|
const result = numberFormat(BytesPipe.calculateResult(format, bytes));
|
||||||
|
|
||||||
return BytesPipe.formatResult(result, to);
|
return BytesPipe.formatResult(result, to, plaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in BytesPipe.formats) {
|
for (const key in BytesPipe.formats) {
|
||||||
@ -51,13 +51,17 @@ export class BytesPipe implements PipeTransform {
|
|||||||
|
|
||||||
const result = numberFormat(BytesPipe.calculateResult(format, bytes));
|
const result = numberFormat(BytesPipe.calculateResult(format, bytes));
|
||||||
|
|
||||||
return BytesPipe.formatResult(result, key);
|
return BytesPipe.formatResult(result, key, plaintext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static formatResult(result: string, unit: string): string {
|
static formatResult(result: string, unit: string, plaintext): string {
|
||||||
return `${result} <span class="symbol">${unit}</span>`;
|
if (plaintext) {
|
||||||
|
return `${result} ${unit}`;
|
||||||
|
} else {
|
||||||
|
return `${result} <span class="symbol">${unit}</span>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static calculateResult(format: { max: number, prev?: ByteUnit }, bytes: number) {
|
static calculateResult(format: { max: number, prev?: ByteUnit }, bytes: number) {
|
||||||
|
@ -87,6 +87,7 @@ import { AccelerateFeeGraphComponent } from '../components/accelerate-preview/ac
|
|||||||
import { MempoolErrorComponent } from './components/mempool-error/mempool-error.component';
|
import { MempoolErrorComponent } from './components/mempool-error/mempool-error.component';
|
||||||
|
|
||||||
import { BlockViewComponent } from '../components/block-view/block-view.component';
|
import { BlockViewComponent } from '../components/block-view/block-view.component';
|
||||||
|
import { EightBlocksComponent } from '../components/eight-blocks/eight-blocks.component';
|
||||||
import { MempoolBlockViewComponent } from '../components/mempool-block-view/mempool-block-view.component';
|
import { MempoolBlockViewComponent } from '../components/mempool-block-view/mempool-block-view.component';
|
||||||
import { MempoolBlockOverviewComponent } from '../components/mempool-block-overview/mempool-block-overview.component';
|
import { MempoolBlockOverviewComponent } from '../components/mempool-block-overview/mempool-block-overview.component';
|
||||||
import { ClockchainComponent } from '../components/clockchain/clockchain.component';
|
import { ClockchainComponent } from '../components/clockchain/clockchain.component';
|
||||||
@ -126,6 +127,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
ColoredPriceDirective,
|
ColoredPriceDirective,
|
||||||
BlockchainComponent,
|
BlockchainComponent,
|
||||||
BlockViewComponent,
|
BlockViewComponent,
|
||||||
|
EightBlocksComponent,
|
||||||
MempoolBlockViewComponent,
|
MempoolBlockViewComponent,
|
||||||
MempoolBlocksComponent,
|
MempoolBlocksComponent,
|
||||||
BlockchainBlocksComponent,
|
BlockchainBlocksComponent,
|
||||||
@ -179,6 +181,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
CalculatorComponent,
|
CalculatorComponent,
|
||||||
BitcoinsatoshisPipe,
|
BitcoinsatoshisPipe,
|
||||||
BlockViewComponent,
|
BlockViewComponent,
|
||||||
|
EightBlocksComponent,
|
||||||
MempoolBlockViewComponent,
|
MempoolBlockViewComponent,
|
||||||
MempoolBlockOverviewComponent,
|
MempoolBlockOverviewComponent,
|
||||||
ClockchainComponent,
|
ClockchainComponent,
|
||||||
@ -202,6 +205,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
BytesPipe,
|
||||||
VbytesPipe,
|
VbytesPipe,
|
||||||
WuBytesPipe,
|
WuBytesPipe,
|
||||||
RelativeUrlPipe,
|
RelativeUrlPipe,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user