Add standalone block visualization page
This commit is contained in:
parent
d0696628b2
commit
f3fc774c2d
@ -4,6 +4,7 @@ import { AppPreloadingStrategy } from './app.preloading-strategy'
|
|||||||
import { StartComponent } from './components/start/start.component';
|
import { StartComponent } from './components/start/start.component';
|
||||||
import { TransactionComponent } from './components/transaction/transaction.component';
|
import { TransactionComponent } from './components/transaction/transaction.component';
|
||||||
import { BlockComponent } from './components/block/block.component';
|
import { BlockComponent } from './components/block/block.component';
|
||||||
|
import { BlockViewComponent } from './components/block-view/block-view.component';
|
||||||
import { ClockComponent } from './components/clock/clock.component';
|
import { ClockComponent } from './components/clock/clock.component';
|
||||||
import { AddressComponent } from './components/address/address.component';
|
import { AddressComponent } from './components/address/address.component';
|
||||||
import { MasterPageComponent } from './components/master-page/master-page.component';
|
import { MasterPageComponent } from './components/master-page/master-page.component';
|
||||||
@ -373,6 +374,10 @@ let routes: Routes = [
|
|||||||
path: 'clock/:mode/:index',
|
path: 'clock/:mode/:index',
|
||||||
component: ClockComponent,
|
component: ClockComponent,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'view/block/:id',
|
||||||
|
component: BlockViewComponent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'status',
|
path: 'status',
|
||||||
data: { networks: ['bitcoin', 'liquid'] },
|
data: { networks: ['bitcoin', 'liquid'] },
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
<div class="block-wrapper">
|
||||||
|
<div class="block-container">
|
||||||
|
<app-block-overview-graph
|
||||||
|
#blockGraph
|
||||||
|
[isLoading]="false"
|
||||||
|
[resolution]="resolution"
|
||||||
|
[blockLimit]="stateService.blockVSize"
|
||||||
|
[orientation]="'top'"
|
||||||
|
[flip]="false"
|
||||||
|
[disableSpinner]="true"
|
||||||
|
></app-block-overview-graph>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,22 @@
|
|||||||
|
.block-wrapper {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: #181b2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-container {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 100vw;
|
||||||
|
max-width: 100vh;
|
||||||
|
height: 100vh;
|
||||||
|
padding: 0;
|
||||||
|
margin: auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
* {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
176
frontend/src/app/components/block-view/block-view.component.ts
Normal file
176
frontend/src/app/components/block-view/block-view.component.ts
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import { Component, OnInit, OnDestroy, ViewChild, HostListener } from '@angular/core';
|
||||||
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
|
import { switchMap, tap, throttleTime, catchError, shareReplay, startWith, pairwise, filter } from 'rxjs/operators';
|
||||||
|
import { of, Subscription, asyncScheduler } from 'rxjs';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { SeoService } from '../../services/seo.service';
|
||||||
|
import { OpenGraphService } from '../../services/opengraph.service';
|
||||||
|
import { BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
|
import { ApiService } from '../../services/api.service';
|
||||||
|
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||||
|
import { BlockOverviewGraphComponent } from '../block-overview-graph/block-overview-graph.component';
|
||||||
|
|
||||||
|
function bestFitResolution(min, max, n) {
|
||||||
|
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-block-view',
|
||||||
|
templateUrl: './block-view.component.html',
|
||||||
|
styleUrls: ['./block-view.component.scss']
|
||||||
|
})
|
||||||
|
export class BlockViewComponent implements OnInit, OnDestroy {
|
||||||
|
network = '';
|
||||||
|
block: BlockExtended;
|
||||||
|
blockHeight: number;
|
||||||
|
blockHash: string;
|
||||||
|
rawId: string;
|
||||||
|
isLoadingBlock = true;
|
||||||
|
strippedTransactions: TransactionStripped[];
|
||||||
|
isLoadingOverview = true;
|
||||||
|
autofit: boolean = false;
|
||||||
|
resolution: number = 80;
|
||||||
|
|
||||||
|
overviewSubscription: Subscription;
|
||||||
|
networkChangedSubscription: Subscription;
|
||||||
|
queryParamsSubscription: Subscription;
|
||||||
|
|
||||||
|
@ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private electrsApiService: ElectrsApiService,
|
||||||
|
public stateService: StateService,
|
||||||
|
private seoService: SeoService,
|
||||||
|
private openGraphService: OpenGraphService,
|
||||||
|
private apiService: ApiService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.network = this.stateService.network;
|
||||||
|
|
||||||
|
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
||||||
|
this.autofit = params.autofit === 'true';
|
||||||
|
if (this.autofit) {
|
||||||
|
this.onResize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const block$ = this.route.paramMap.pipe(
|
||||||
|
switchMap((params: ParamMap) => {
|
||||||
|
this.rawId = params.get('id') || '';
|
||||||
|
|
||||||
|
const blockHash: string = params.get('id') || '';
|
||||||
|
this.block = undefined;
|
||||||
|
|
||||||
|
let isBlockHeight = false;
|
||||||
|
if (/^[0-9]+$/.test(blockHash)) {
|
||||||
|
isBlockHeight = true;
|
||||||
|
} else {
|
||||||
|
this.blockHash = blockHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoadingBlock = true;
|
||||||
|
this.isLoadingOverview = true;
|
||||||
|
|
||||||
|
if (isBlockHeight) {
|
||||||
|
return this.electrsApiService.getBlockHashFromHeight$(parseInt(blockHash, 10))
|
||||||
|
.pipe(
|
||||||
|
switchMap((hash) => {
|
||||||
|
if (hash) {
|
||||||
|
this.blockHash = hash;
|
||||||
|
return this.apiService.getBlock$(hash);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError(() => {
|
||||||
|
return of(null);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.apiService.getBlock$(blockHash);
|
||||||
|
}),
|
||||||
|
filter((block: BlockExtended | void) => block != null),
|
||||||
|
tap((block: BlockExtended) => {
|
||||||
|
this.block = block;
|
||||||
|
this.blockHeight = block.height;
|
||||||
|
|
||||||
|
this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`);
|
||||||
|
if( this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet' ) {
|
||||||
|
this.seoService.setDescription($localize`:@@meta.description.liquid.block:See size, weight, fee range, included transactions, and more for Liquid${seoDescriptionNetwork(this.stateService.network)} block ${block.height}:BLOCK_HEIGHT: (${block.id}:BLOCK_ID:).`);
|
||||||
|
} else {
|
||||||
|
this.seoService.setDescription($localize`:@@meta.description.bitcoin.block:See size, weight, fee range, included transactions, audit (expected v actual), and more for Bitcoin${seoDescriptionNetwork(this.stateService.network)} block ${block.height}:BLOCK_HEIGHT: (${block.id}:BLOCK_ID:).`);
|
||||||
|
}
|
||||||
|
this.isLoadingBlock = false;
|
||||||
|
this.isLoadingOverview = true;
|
||||||
|
}),
|
||||||
|
shareReplay(1)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.overviewSubscription = block$.pipe(
|
||||||
|
switchMap((block) => this.apiService.getStrippedBlockTransactions$(block.id)
|
||||||
|
.pipe(
|
||||||
|
catchError(() => {
|
||||||
|
return of([]);
|
||||||
|
}),
|
||||||
|
switchMap((transactions) => {
|
||||||
|
return of(transactions);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subscribe((transactions: TransactionStripped[]) => {
|
||||||
|
this.strippedTransactions = transactions;
|
||||||
|
this.isLoadingOverview = false;
|
||||||
|
if (this.blockGraph) {
|
||||||
|
this.blockGraph.destroy();
|
||||||
|
this.blockGraph.setup(this.strippedTransactions);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.isLoadingOverview = false;
|
||||||
|
if (this.blockGraph) {
|
||||||
|
this.blockGraph.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.networkChangedSubscription = this.stateService.networkChanged$
|
||||||
|
.subscribe((network) => this.network = network);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('window:resize', ['$event'])
|
||||||
|
onResize(): void {
|
||||||
|
if (this.autofit) {
|
||||||
|
this.resolution = bestFitResolution(64, 96, Math.min(window.innerWidth, window.innerHeight));
|
||||||
|
console.log('resized, new resolution ', this.resolution, window.innerWidth, window.innerHeight);
|
||||||
|
// if (this.blockGraph && this.strippedTransactions) {
|
||||||
|
// this.blockGraph.destroy();
|
||||||
|
// this.blockGraph.setup(this.strippedTransactions);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.overviewSubscription) {
|
||||||
|
this.overviewSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
if (this.networkChangedSubscription) {
|
||||||
|
this.networkChangedSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
if (this.queryParamsSubscription) {
|
||||||
|
this.queryParamsSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -97,6 +97,7 @@ import { AcceleratePreviewComponent } from '../components/accelerate-preview/acc
|
|||||||
import { AccelerateFeeGraphComponent } from '../components/accelerate-preview/accelerate-fee-graph.component';
|
import { AccelerateFeeGraphComponent } from '../components/accelerate-preview/accelerate-fee-graph.component';
|
||||||
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 { 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';
|
||||||
import { ClockFaceComponent } from '../components/clock-face/clock-face.component';
|
import { ClockFaceComponent } from '../components/clock-face/clock-face.component';
|
||||||
@ -134,6 +135,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
FiatCurrencyPipe,
|
FiatCurrencyPipe,
|
||||||
ColoredPriceDirective,
|
ColoredPriceDirective,
|
||||||
BlockchainComponent,
|
BlockchainComponent,
|
||||||
|
BlockViewComponent,
|
||||||
MempoolBlocksComponent,
|
MempoolBlocksComponent,
|
||||||
BlockchainBlocksComponent,
|
BlockchainBlocksComponent,
|
||||||
AmountComponent,
|
AmountComponent,
|
||||||
@ -196,6 +198,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
AccelerateFeeGraphComponent,
|
AccelerateFeeGraphComponent,
|
||||||
CalculatorComponent,
|
CalculatorComponent,
|
||||||
BitcoinsatoshisPipe,
|
BitcoinsatoshisPipe,
|
||||||
|
BlockViewComponent,
|
||||||
MempoolBlockOverviewComponent,
|
MempoolBlockOverviewComponent,
|
||||||
ClockchainComponent,
|
ClockchainComponent,
|
||||||
ClockComponent,
|
ClockComponent,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user