Liquid and Testnet now accessable from the main site

fixes #35
This commit is contained in:
softsimon 2020-05-09 20:37:50 +07:00
parent 20c7ee98e7
commit 1feb985bec
No known key found for this signature in database
GPG Key ID: 488D7DCFB5A430D7
45 changed files with 452 additions and 162 deletions

View File

@ -63,14 +63,177 @@ const routes: Routes = [
},
],
},
{
path: 'liquid',
component: MasterPageComponent,
children: [
{
path: '',
component: StartComponent,
children: [
{
path: '',
component: LatestBlocksComponent
},
{
path: 'tx/:id',
component: TransactionComponent
},
{
path: 'block/:id',
component: BlockComponent
},
{
path: 'mempool-block/:id',
component: MempoolBlockComponent
},
],
},
{
path: 'graphs',
component: StatisticsComponent,
},
{
path: 'tv',
component: TelevisionComponent,
},
{
path: 'contributors',
component: AboutComponent,
},
{
path: 'address/:id',
children: [],
component: AddressComponent
},
{
path: 'asset/:id',
component: AssetComponent
},
{
path: 'assets',
component: AssetsComponent,
},
{
path: '**',
redirectTo: ''
},
],
},
{
path: 'liquid',
component: MasterPageComponent,
children: [
{
path: '',
component: StartComponent,
children: [
{
path: '',
component: LatestBlocksComponent
},
{
path: 'tx/:id',
component: TransactionComponent
},
{
path: 'block/:id',
component: BlockComponent
},
{
path: 'mempool-block/:id',
component: MempoolBlockComponent
},
],
},
{
path: 'graphs',
component: StatisticsComponent,
},
{
path: 'contributors',
component: AboutComponent,
},
{
path: 'address/:id',
children: [],
component: AddressComponent
},
{
path: 'asset/:id',
component: AssetComponent
},
{
path: 'assets',
component: AssetsComponent,
},
{
path: '**',
redirectTo: ''
},
],
},
{
path: 'testnet',
component: MasterPageComponent,
children: [
{
path: '',
component: StartComponent,
children: [
{
path: '',
component: LatestBlocksComponent
},
{
path: 'tx/:id',
component: TransactionComponent
},
{
path: 'block/:id',
component: BlockComponent
},
{
path: 'mempool-block/:id',
component: MempoolBlockComponent
},
],
},
{
path: 'graphs',
component: StatisticsComponent,
},
{
path: 'contributors',
component: AboutComponent,
},
{
path: 'address/:id',
children: [],
component: AddressComponent
},
{
path: '**',
redirectTo: ''
},
],
},
{
path: 'tv',
component: TelevisionComponent,
},
{
path: 'liquid-tv',
component: TelevisionComponent,
},
{
path: 'testnet-tv',
component: TelevisionComponent,
},
{
path: '**',
redirectTo: ''
}
},
];
@NgModule({

View File

@ -48,6 +48,7 @@ import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.
import { AssetComponent } from './components/asset/asset.component';
import { ScriptpubkeyTypePipe } from './pipes/scriptpubkey-type-pipe/scriptpubkey-type.pipe';
import { AssetsComponent } from './assets/assets.component';
import { RelativeUrlPipe } from './pipes/relative-url/relative-url.pipe';
@NgModule({
declarations: [
@ -86,6 +87,7 @@ import { AssetsComponent } from './assets/assets.component';
AssetComponent,
ScriptpubkeyTypePipe,
AssetsComponent,
RelativeUrlPipe,
],
imports: [
BrowserModule,

View File

@ -18,8 +18,8 @@
<td class="td-name">{{ asset.name }}</td>
<td>{{ asset.ticker }}</td>
<td class="d-none d-md-block"><a *ngIf="asset.entity" target="_blank" href="{{ 'http://' + asset.entity.domain }}">{{ asset.entity.domain }}</a></td>
<td><a [routerLink]="['/asset/', asset.asset_id]">{{ asset.asset_id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.asset_id"></app-clipboard></td>
<td class="d-none d-lg-block"><ng-template [ngIf]="asset.issuance_txin"><a [routerLink]="['/tx/', asset.issuance_txin.txid]">{{ asset.issuance_txin.txid | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.issuance_txin.txid"></app-clipboard></ng-template></td>
<td><a [routerLink]="['/asset/' | relativeUrl, asset.asset_id]">{{ asset.asset_id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.asset_id"></app-clipboard></td>
<td class="d-none d-lg-block"><ng-template [ngIf]="asset.issuance_txin"><a [routerLink]="['/tx/' | relativeUrl, asset.issuance_txin.txid]">{{ asset.issuance_txin.txid | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.issuance_txin.txid"></app-clipboard></ng-template></td>
</tr>
</tbody>
</table>

View File

@ -1,2 +1,2 @@
<span *ngIf="multisig" class="badge badge-pill badge-warning">multisig {{ multisigM }} of {{ multisigN }}</span>
<span *ngIf="lnChannelClose" class="badge badge-pill badge-warning">Lightning Channel Force Close</span>
<span *ngIf="secondLayerClose" class="badge badge-pill badge-warning">Layer2 Peg-out</span>

View File

@ -16,7 +16,7 @@ export class AddressLabelsComponent implements OnInit {
multisigM: number;
multisigN: number;
lnChannelClose = false;
secondLayerClose = false;
constructor() { }
@ -33,12 +33,16 @@ export class AddressLabelsComponent implements OnInit {
if (this.vin.inner_witnessscript_asm.indexOf('OP_CHECKMULTISIG') > -1) {
const matches = this.getMatches(this.vin.inner_witnessscript_asm, /OP_PUSHNUM_([0-9])/g, 1);
this.multisig = true;
this.multisigM = matches[0];
this.multisigN = matches[1];
this.multisigM = parseInt(matches[0], 10);
this.multisigN = parseInt(matches[1], 10);
if (this.multisigM === 1 && this.multisigN === 1) {
this.multisig = false;
}
}
if (/OP_IF (.+) OP_ELSE (.+) OP_CSV OP_DROP/.test(this.vin.inner_witnessscript_asm)) {
this.lnChannelClose = true;
this.secondLayerClose = true;
}
}

View File

@ -1,6 +1,6 @@
<div class="container-xl">
<h1 style="float: left;">Address</h1>
<a [routerLink]="['/address/', addressString]" style="line-height: 56px; margin-left: 10px;">
<a [routerLink]="['/address/' | relativeUrl, addressString]" style="line-height: 56px; margin-left: 10px;">
<span class="d-inline d-lg-none">{{ addressString | shortenString : 24 }}</span>
<span class="d-none d-lg-inline">{{ addressString }}</span>
</a>

View File

@ -9,7 +9,6 @@ import { AudioService } from 'src/app/services/audio.service';
import { ApiService } from 'src/app/services/api.service';
import { of, merge, Subscription } from 'rxjs';
import { SeoService } from 'src/app/services/seo.service';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-address',
@ -17,7 +16,7 @@ import { environment } from 'src/environments/environment';
styleUrls: ['./address.component.scss']
})
export class AddressComponent implements OnInit, OnDestroy {
network = environment.network;
network = '';
address: Address;
addressString: string;
@ -48,6 +47,7 @@ export class AddressComponent implements OnInit, OnDestroy {
) { }
ngOnInit() {
this.stateService.networkChanged$.subscribe((network) => this.network = network);
this.websocketService.want(['blocks', 'stats', 'mempool-blocks']);
this.mainSubscription = this.route.paramMap

View File

@ -6,6 +6,7 @@
Confidential
</ng-template>
<ng-template #default>
{{ satoshis / 100000000 | number : '1.8-8' }} <ng-template [ngIf]="network === 'liquid'">L-</ng-template>BTC
{{ satoshis / 100000000 | number : digitsInfo }} <ng-template [ngIf]="network === 'liquid'">L-BTC</ng-template>
<ng-template [ngIf]="network === 'testnet'">tBTC</ng-template>
</ng-template>
</ng-template>

View File

@ -1,7 +1,6 @@
import { Component, OnInit, Input, ChangeDetectionStrategy } from '@angular/core';
import { StateService } from '../../services/state.service';
import { Observable } from 'rxjs';
import { environment } from '../../../environments/environment';
@Component({
selector: 'app-amount',
@ -12,9 +11,10 @@ import { environment } from '../../../environments/environment';
export class AmountComponent implements OnInit {
conversions$: Observable<any>;
viewFiat$: Observable<boolean>;
network = environment.network;
network = '';
@Input() satoshis: number;
@Input() digitsInfo = '1.8-8';
constructor(
private stateService: StateService,
@ -23,6 +23,7 @@ export class AmountComponent implements OnInit {
ngOnInit() {
this.viewFiat$ = this.stateService.viewFiat$.asObservable();
this.conversions$ = this.stateService.conversions$.asObservable();
this.stateService.networkChanged$.subscribe((network) => this.network = network);
}
}

View File

@ -1,41 +1,18 @@
import { Component, OnInit } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { WebsocketService } from '../../services/websocket.service';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
network = environment.network;
export class AppComponent {
network = '';
link: HTMLLinkElement;
constructor(
public router: Router,
private websocketService: WebsocketService,
) { }
ngOnInit() {
this.router.events.subscribe((val) => {
if (val instanceof NavigationEnd) {
if (this.network === 'liquid' || this.network === 'testnet') {
this.updateCanonicalUrlElement('https://' + this.network + '.mempool.ninja' + location.pathname);
} else {
this.updateCanonicalUrlElement('https://mempool.space' + location.pathname);
}
}
});
}
updateCanonicalUrlElement(url) {
if (!this.link) {
this.link = window.document.createElement('link');
this.link.setAttribute('rel', 'canonical');
window.document.head.appendChild(this.link);
}
this.link.setAttribute('href', url);
}
}

View File

@ -1,6 +1,6 @@
<div class="container-xl">
<h1 style="float: left;">Asset</h1>
<a [routerLink]="['/asset/', assetString]" style="line-height: 56px; margin-left: 10px;">
<a [routerLink]="['/asset/' | relativeUrl, assetString]" style="line-height: 56px; margin-left: 10px;">
<span class="d-inline d-lg-none">{{ assetString | shortenString : 24 }}</span>
<span class="d-none d-lg-inline">{{ assetString }}</span>
</a>
@ -30,7 +30,7 @@
</tr>
<tr *ngIf="!isNativeAsset">
<td>Issuance tx</td>
<td><a [routerLink]="['/tx/', asset.issuance_txin.txid]">{{ asset.issuance_txin.txid | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.issuance_txin.txid"></app-clipboard></td>
<td><a [routerLink]="['/tx/' | relativeUrl, asset.issuance_txin.txid]">{{ asset.issuance_txin.txid | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.issuance_txin.txid"></app-clipboard></td>
</tr>
</tbody>
</table>

View File

@ -18,7 +18,7 @@ import { AssetsService } from 'src/app/services/assets.service';
styleUrls: ['./asset.component.scss']
})
export class AssetComponent implements OnInit, OnDestroy {
network = environment.network;
network = '';
nativeAssetId = environment.nativeAssetId;
asset: Asset;
@ -54,6 +54,7 @@ export class AssetComponent implements OnInit, OnDestroy {
ngOnInit() {
this.websocketService.want(['blocks', 'stats', 'mempool-blocks']);
this.stateService.networkChanged$.subscribe((network) => this.network = network);
this.mainSubscription = this.route.paramMap
.pipe(

View File

@ -1,7 +1,7 @@
<div class="container-xl">
<div class="title-block">
<h1>Block <ng-template [ngIf]="blockHeight"><a [routerLink]="['/block/', blockHash]">{{ blockHeight }}</a></ng-template></h1>
<h1>Block <ng-template [ngIf]="blockHeight"><a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a></ng-template></h1>
</div>
<ng-template [ngIf]="!isLoadingBlock && !error">
@ -40,7 +40,7 @@
<tbody>
<tr>
<td class="td-width">Hash</td>
<td><a [routerLink]="['/block/', block.id]" title="{{ block.id }}">{{ block.id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="block.id"></app-clipboard></td>
<td><a [routerLink]="['/block/' | relativeUrl, block.id]" title="{{ block.id }}">{{ block.id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="block.id"></app-clipboard></td>
</tr>
<tr *ngIf="block.medianFee !== undefined">
<td>Median fee</td>
@ -49,12 +49,12 @@
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
<tr>
<td>Total fees</td>
<td><span title="{{ fees | number: '1.2-8' }} BTC">{{ fees | number: '1.2-2' }} <ng-template [ngIf]="network === 'liquid'">L-</ng-template>BTC</span> (<app-fiat [value]="fees * 100000000" digitsInfo="1.0-0"></app-fiat>)</td>
<td><app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-8"></app-amount> (<app-fiat [value]="fees * 100000000" digitsInfo="1.0-0"></app-fiat>)</td>
</tr>
<tr>
<td>Reward + fees:</td>
<td>
<span title="{{ blockSubsidy + fees | number: '1.2-8' }} BTC">{{ blockSubsidy + fees | number: '1.2-2' }} <ng-template [ngIf]="network === 'liquid'">L-</ng-template>BTC</span> (<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>)
<app-amount [satoshis]="(blockSubsidy + fees) * 100000000" digitsInfo="1.2-8"></app-amount> (<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>)
</td>
</tr>
</ng-template>

View File

@ -6,7 +6,6 @@ import { Block, Transaction, Vout } from '../../interfaces/electrs.interface';
import { of } from 'rxjs';
import { StateService } from '../../services/state.service';
import { SeoService } from 'src/app/services/seo.service';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-block',
@ -14,7 +13,7 @@ import { environment } from 'src/environments/environment';
styleUrls: ['./block.component.scss']
})
export class BlockComponent implements OnInit, OnDestroy {
network = environment.network;
network = '';
block: Block;
blockHeight: number;
blockHash: string;
@ -92,6 +91,9 @@ export class BlockComponent implements OnInit, OnDestroy {
this.stateService.blocks$
.subscribe((block) => this.latestBlock = block);
this.stateService.networkChanged$
.subscribe((network) => this.network = network);
}
ngOnDestroy() {

View File

@ -1,9 +1,9 @@
<div class="blocks-container" *ngIf="blocks.length">
<div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" >
<div class="text-center bitcoin-block mined-block" [class.blink-bg]="block.height % 210000 === 0" id="bitcoin-block-{{ block.height }}" [ngStyle]="getStyleForBlock(block)">
<a [routerLink]="['/block/', block.id]" [state]="{ data: { block: block } }" class="blockLink">&nbsp;</a>
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" class="blockLink">&nbsp;</a>
<div class="block-height">
<a [routerLink]="['/block/', block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
</div>
<div class="block-body">
<div class="fees">

View File

@ -1,11 +1,13 @@
<div class="text-center" class="blockchain-wrapper">
<div class="position-container">
<span [hidden]="isLoading">
<app-mempool-blocks></app-mempool-blocks>
<app-blockchain-blocks></app-blockchain-blocks>
<div id="divider"></div>
</span>
<div id="divider" *ngIf="!isLoading; else loadingTmpl"></div>
<ng-template #loadingTmpl>
<ng-template [ngIf]="isLoading">
<div class="loading-block">
<h3>Waiting for blocks...</h3>
<br>

View File

@ -1,6 +1,4 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import { Component, OnInit } from '@angular/core';
import { StateService } from 'src/app/services/state.service';
@Component({
@ -8,9 +6,7 @@ import { StateService } from 'src/app/services/state.service';
templateUrl: './blockchain.component.html',
styleUrls: ['./blockchain.component.scss']
})
export class BlockchainComponent implements OnInit, OnDestroy {
blocksSubscription: Subscription;
export class BlockchainComponent implements OnInit {
txTrackingLoading = false;
txShowTxNotFound = false;
isLoading = true;
@ -20,14 +16,7 @@ export class BlockchainComponent implements OnInit, OnDestroy {
) {}
ngOnInit() {
this.blocksSubscription = this.stateService.blocks$
.pipe(
take(1)
)
.subscribe(() => this.isLoading = false);
}
ngOnDestroy() {
this.blocksSubscription.unsubscribe();
this.stateService.blocks$.subscribe(() => this.isLoading = false);
this.stateService.networkChanged$.subscribe(() => this.isLoading = true);
}
}

View File

@ -1,5 +1,5 @@
<div class="text-center pb-1" *ngIf="network === 'mempool' && latestBlockHeight && latestBlockHeight < 630000">
<div class="text-center pb-1" *ngIf="network === '' && latestBlockHeight && latestBlockHeight < 630000">
<h3>Quantitative Hardening in {{ 630000 - latestBlockHeight }} blocks (~<app-timespan [time]="(630000 - latestBlockHeight) * 10 * 60"></app-timespan>)</h3>
</div>
@ -17,7 +17,7 @@
</thead>
<tbody>
<tr *ngFor="let block of blocks; let i= index; trackBy: trackByBlock">
<td><a [routerLink]="['/block', block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a></td>
<td><a [routerLink]="['/block' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a></td>
<td class="d-none d-md-block">{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}</td>
<td><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since> ago</td>
<td class="d-none d-lg-block">{{ block.tx_count | number }}</td>

View File

@ -4,7 +4,6 @@ import { StateService } from '../../services/state.service';
import { Block } from '../../interfaces/electrs.interface';
import { Subscription } from 'rxjs';
import { SeoService } from 'src/app/services/seo.service';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-latest-blocks',
@ -12,7 +11,7 @@ import { environment } from 'src/environments/environment';
styleUrls: ['./latest-blocks.component.scss'],
})
export class LatestBlocksComponent implements OnInit, OnDestroy {
network = environment.network;
network = '';
blocks: any[] = [];
blockSubscription: Subscription;
@ -32,6 +31,7 @@ export class LatestBlocksComponent implements OnInit, OnDestroy {
ngOnInit() {
this.seoService.resetTitle();
this.stateService.networkChanged$.subscribe((network) => this.network = network);
this.blockSubscription = this.stateService.blocks$
.subscribe((block) => {

View File

@ -6,26 +6,37 @@
<div class="badge badge-warning connection-badge" style="left: 30px;" *ngIf="connectionState === 1">Reconnecting...</div>
</a>
<div class="btn-group" style="margin-right: 16px;">
<button type="button" (click)="networkDropdownHidden = !networkDropdownHidden" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu" [class.d-block]="!networkDropdownHidden">
<a class="dropdown-item" routerLink="/">Mainnet</a>
<a class="dropdown-item" routerLink="/liquid">Liquid</a>
<a class="dropdown-item" routerLink="/testnet">Testnet</a>
</div>
</div>
<button class="navbar-toggler" type="button" (click)="collapse()" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse" id="navbarCollapse" [ngClass]="{'show': navCollapsed}">
<ul class="navbar-nav mr-auto">
<ul class="navbar-nav mr-auto {{ network }}">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<a class="nav-link" routerLink="/" (click)="collapse()">Blockchain</a>
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()">Blockchain</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/graphs" (click)="collapse()">Graphs</a>
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()">Graphs</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/tv" (click)="collapse()">TV view &nbsp;<img src="./resources/expand.png" width="15"/></a>
<a class="nav-link" [routerLink]="[tvViewRoute]" (click)="collapse()">TV view &nbsp;<img src="./resources/expand.png" width="15"/></a>
</li>
<li *ngIf="network === 'liquid'" class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/assets" (click)="collapse()">Assets</a>
<a class="nav-link" [routerLink]="['/assets' | relativeUrl]" (click)="collapse()">Assets</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/contributors" (click)="collapse()">Contributors</a>
<a class="nav-link" [routerLink]="['/contributors' | relativeUrl]" (click)="collapse()">Contributors</a>
</li>
</ul>
<app-search-form location="top" (searchTriggered)="collapse()"></app-search-form>

View File

@ -1,6 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, HostListener } from '@angular/core';
import { StateService } from '../../services/state.service';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-master-page',
@ -8,20 +7,43 @@ import { environment } from 'src/environments/environment';
styleUrls: ['./master-page.component.scss']
})
export class MasterPageComponent implements OnInit {
network = environment.network;
network = '';
tvViewRoute = '/tv';
navCollapsed = false;
connectionState = 2;
networkDropdownHidden = true;
constructor(
private stateService: StateService,
) { }
@HostListener('document:click', ['$event'])
documentClick(event: any): void {
if (!event.target.classList.contains('dropdown-toggle')) {
this.networkDropdownHidden = true;
}
}
ngOnInit() {
this.stateService.connectionState$
.subscribe((state) => {
this.connectionState = state;
});
this.stateService.networkChanged$
.subscribe((network) => {
this.network = network;
if (network === 'testnet') {
this.tvViewRoute = '/testnet-tv';
} else if (network === 'liquid') {
this.tvViewRoute = '/liquid-tv';
} else {
this.tvViewRoute = '/tv';
}
});
}
collapse(): void {

View File

@ -5,7 +5,6 @@ import { switchMap, map, tap } from 'rxjs/operators';
import { MempoolBlock } from 'src/app/interfaces/websocket.interface';
import { Observable } from 'rxjs';
import { SeoService } from 'src/app/services/seo.service';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-mempool-block',
@ -13,7 +12,7 @@ import { environment } from 'src/environments/environment';
styleUrls: ['./mempool-block.component.scss']
})
export class MempoolBlockComponent implements OnInit, OnDestroy {
network = environment.network;
network = '';
mempoolBlockIndex: number;
mempoolBlock$: Observable<MempoolBlock>;
@ -43,6 +42,9 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
this.stateService.markBlock$.next({ mempoolBlockIndex: this.mempoolBlockIndex });
})
);
this.stateService.networkChanged$
.subscribe((network) => this.network = network);
}
ngOnDestroy(): void {

View File

@ -2,7 +2,7 @@
<div class="flashing">
<div *ngFor="let projectedBlock of mempoolBlocks; let i = index; trackBy: trackByFn">
<div class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="getStyleForMempoolBlockAtIndex(i)">
<a [routerLink]="['/mempool-block/', i]" class="blockLink">&nbsp;</a>
<a [routerLink]="['/mempool-block/' | relativeUrl, i]" class="blockLink">&nbsp;</a>
<div class="block-body" *ngIf="mempoolBlocks?.length">
<div class="fees">
<span class="yellow-color">~{{ projectedBlock.medianFee | number:'1.0-0' }} sat/vB</span>

View File

@ -4,7 +4,7 @@ import { MempoolBlock } from 'src/app/interfaces/websocket.interface';
import { StateService } from 'src/app/services/state.service';
import { Router } from '@angular/router';
import { take } from 'rxjs/operators';
import { environment } from '../../../environments/environment';
@Component({
selector: 'app-mempool-blocks',
templateUrl: './mempool-blocks.component.html',
@ -14,7 +14,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
mempoolBlocks: MempoolBlock[];
mempoolBlocksFull: MempoolBlock[];
mempoolBlocksSubscription: Subscription;
network = environment.network;
network = '';
blockWidth = 125;
blockPadding = 30;
@ -54,6 +54,9 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
}
this.calculateTransactionPosition();
});
this.stateService.networkChanged$
.subscribe((network) => this.network = network);
}
@HostListener('window:resize', ['$event'])

View File

@ -2,8 +2,8 @@ import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, O
import { formatDate } from '@angular/common';
import { VbytesPipe } from 'src/app/pipes/bytes-pipe/vbytes.pipe';
import * as Chartist from 'chartist';
import { environment } from 'src/environments/environment';
import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
import { StateService } from 'src/app/services/state.service';
@Component({
selector: 'app-mempool-graph',
@ -13,16 +13,18 @@ import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
export class MempoolGraphComponent implements OnInit, OnChanges {
@Input() data;
network = environment.network;
network = '';
mempoolVsizeFeesOptions: any;
mempoolVsizeFeesData: any;
constructor(
private vbytesPipe: VbytesPipe,
private stateService: StateService,
@Inject(LOCALE_ID) private locale: string,
) { }
ngOnInit(): void {
this.stateService.networkChanged$.subscribe((network) => this.network = network);
const labelInterpolationFnc = (value: any, index: any) => {
return index % 6 === 0 ? formatDate(value, 'HH:mm', this.locale) : null;
};

View File

@ -1,8 +1,8 @@
import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { environment } from 'src/environments/environment';
import { AssetsService } from 'src/app/services/assets.service';
import { StateService } from 'src/app/services/state.service';
@Component({
selector: 'app-search-form',
@ -11,7 +11,7 @@ import { AssetsService } from 'src/app/services/assets.service';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SearchFormComponent implements OnInit {
network = environment.network;
network = '';
assets: object;
searchForm: FormGroup;
@ -25,9 +25,12 @@ export class SearchFormComponent implements OnInit {
private formBuilder: FormBuilder,
private router: Router,
private assetsService: AssetsService,
private stateService: StateService,
) { }
ngOnInit() {
this.stateService.networkChanged$.subscribe((network) => this.network = network);
this.searchForm = this.formBuilder.group({
searchText: ['', Validators.required],
});
@ -43,16 +46,16 @@ export class SearchFormComponent implements OnInit {
const searchText = this.searchForm.value.searchText.trim();
if (searchText) {
if (this.regexAddress.test(searchText)) {
this.router.navigate(['/address/', searchText]);
this.router.navigate([(this.network ? '/' + this.network : '') + '/address/', searchText]);
this.searchTriggered.emit();
} else if (this.regexBlockhash.test(searchText)) {
this.router.navigate(['/block/', searchText]);
this.router.navigate([(this.network ? '/' + this.network : '') + '/block/', searchText]);
this.searchTriggered.emit();
} else if (this.regexTransaction.test(searchText)) {
if (this.network === 'liquid' && this.assets[searchText]) {
this.router.navigate(['/asset/', searchText]);
this.router.navigate([(this.network ? '/' + this.network : '') + '/asset/', searchText]);
} else {
this.router.navigate(['/tx/', searchText]);
this.router.navigate([(this.network ? '/' + this.network : '') + '/tx/', searchText]);
}
this.searchTriggered.emit();
} else {

View File

@ -19,25 +19,25 @@
<div class="spinner-border text-light bootstrap-spinner" *ngIf="spinnerLoading"></div>
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'2h'" [routerLink]="['/graphs']" fragment="2h"> 2H (LIVE)
<input ngbButton type="radio" [value]="'2h'" [routerLink]="['/graphs' | relativeUrl]" fragment="2h"> 2H (LIVE)
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'24h'" [routerLink]="['/graphs']" fragment="24h"> 24H
<input ngbButton type="radio" [value]="'24h'" [routerLink]="['/graphs' | relativeUrl]" fragment="24h"> 24H
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'1w'" [routerLink]="['/graphs']" fragment="1w"> 1W
<input ngbButton type="radio" [value]="'1w'" [routerLink]="['/graphs' | relativeUrl]" fragment="1w"> 1W
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'1m'" [routerLink]="['/graphs']" fragment="1m"> 1M
<input ngbButton type="radio" [value]="'1m'" [routerLink]="['/graphs' | relativeUrl]" fragment="1m"> 1M
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'3m'" [routerLink]="['/graphs']" fragment="3m"> 3M
<input ngbButton type="radio" [value]="'3m'" [routerLink]="['/graphs' | relativeUrl]" fragment="3m"> 3M
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'6m'" [routerLink]="['/graphs']" fragment="6m"> 6M
<input ngbButton type="radio" [value]="'6m'" [routerLink]="['/graphs' | relativeUrl]" fragment="6m"> 6M
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'1y'" [routerLink]="['/graphs']" fragment="1y"> 1Y
<input ngbButton type="radio" [value]="'1y'" [routerLink]="['/graphs' | relativeUrl]" fragment="1y"> 1Y
</label>
</div>
</form>

View File

@ -12,7 +12,6 @@ import { ApiService } from '../../services/api.service';
import * as Chartist from 'chartist';
import { StateService } from 'src/app/services/state.service';
import { SeoService } from 'src/app/services/seo.service';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-statistics',
@ -20,7 +19,7 @@ import { environment } from 'src/environments/environment';
styleUrls: ['./statistics.component.scss']
})
export class StatisticsComponent implements OnInit {
network = environment.network;
network = '';
loading = true;
spinnerLoading = false;
@ -52,6 +51,8 @@ export class StatisticsComponent implements OnInit {
ngOnInit() {
this.seoService.setTitle('Graphs');
this.stateService.networkChanged$.subscribe((network) => this.network = network);
const labelInterpolationFnc = (value: any, index: any) => {
const nr = 6;

View File

@ -4,7 +4,6 @@ import { OptimizedMempoolStats } from '../../interfaces/node-api.interface';
import { StateService } from 'src/app/services/state.service';
import { ApiService } from 'src/app/services/api.service';
import { SeoService } from 'src/app/services/seo.service';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-television',
@ -13,7 +12,6 @@ import { environment } from 'src/environments/environment';
})
export class TelevisionComponent implements OnInit {
loading = true;
network = environment.network;
mempoolStats: OptimizedMempoolStats[] = [];
mempoolVsizeFeesData: any;

View File

@ -11,7 +11,7 @@
</ng-template>
<div>
<a [routerLink]="['/tx/', txId]" style="line-height: 56px;">
<a [routerLink]="['/tx/' | relativeUrl, txId]" style="line-height: 56px;">
<span class="d-inline d-lg-none">{{ txId | shortenString : 24 }}</span>
<span class="d-none d-lg-inline">{{ txId }}</span>
</a>
@ -33,7 +33,7 @@
<tr>
<td class="td-width">Included in block</td>
<td>
<a [routerLink]="['/block/', tx.status.block_hash]" [state]="{ data: { blockHeight: tx.status.block_height } }">{{ tx.status.block_height }}</a>
<a [routerLink]="['/block/' | relativeUrl, tx.status.block_hash]" [state]="{ data: { blockHeight: tx.status.block_height } }">{{ tx.status.block_height }}</a>
<i> (<app-time-since [time]="tx.status.block_time" [fastRender]="true"></app-time-since> ago)</i>
</td>
</tr>

View File

@ -9,7 +9,6 @@ import { WebsocketService } from '../../services/websocket.service';
import { AudioService } from 'src/app/services/audio.service';
import { ApiService } from 'src/app/services/api.service';
import { SeoService } from 'src/app/services/seo.service';
import { environment } from '../../../environments/environment';
@Component({
selector: 'app-transaction',
@ -17,7 +16,7 @@ import { environment } from '../../../environments/environment';
styleUrls: ['./transaction.component.scss']
})
export class TransactionComponent implements OnInit, OnDestroy {
network = environment.network;
network = '';
tx: Transaction;
txId: string;
feeRating: number;
@ -44,6 +43,8 @@ export class TransactionComponent implements OnInit, OnDestroy {
) { }
ngOnInit() {
this.stateService.networkChanged$.subscribe((network) => this.network = network);
this.subscription = this.route.paramMap.pipe(
switchMap((params: ParamMap) => {
this.txId = params.get('id') || '';

View File

@ -1,6 +1,6 @@
<ng-container *ngFor="let tx of transactions; let i = index; trackBy: trackByFn">
<div *ngIf="!transactionPage" class="header-bg box" style="padding: 10px; margin-bottom: 10px;">
<a [routerLink]="['/tx/', tx.txid]" [state]="{ data: tx }">
<a [routerLink]="['/tx/' | relativeUrl, tx.txid]" [state]="{ data: tx }">
<span style="float: left;" class="d-block d-md-none">{{ tx.txid | shortenString : 16 }}</span>
<span style="float: left;" class="d-none d-md-block">{{ tx.txid }}</span>
</a>
@ -18,21 +18,28 @@
<tbody>
<tr *ngFor="let vin of getFilteredTxVin(tx)">
<td class="arrow-td">
<ng-template [ngIf]="vin.prevout === null" [ngIfElse]="hasPrevout">
<ng-template [ngIf]="vin.prevout === null && !vin.is_pegin" [ngIfElse]="hasPrevout">
<i class="arrow grey"></i>
</ng-template>
<ng-template #hasPrevout>
<a [routerLink]="['/tx/', vin.txid]">
<a *ngIf="vin.is_pegin; else defaultPrevout" [routerLink]="['/tx/', vin.txid]">
<i class="arrow red"></i>
</a>
<ng-template #defaultPrevout>
<a [routerLink]="['/tx/' | relativeUrl, vin.txid]">
<i class="arrow red"></i>
</a>
</ng-template>
</ng-template>
</td>
<td>
<div [ngSwitch]="true">
<ng-container *ngSwitchCase="vin.is_coinbase">Coinbase<ng-template [ngIf]="network !== 'liquid'"> (Newly Generated Coins)</ng-template></ng-container>
<ng-container *ngSwitchCase="vin.is_pegin">PEG IN</ng-container>
<ng-container *ngSwitchCase="vin.is_pegin">
Peg-in
</ng-container>
<ng-container *ngSwitchDefault>
<a [routerLink]="['/address/', vin.prevout.scriptpubkey_address]" title="{{ vin.prevout.scriptpubkey_address }}">
<a [routerLink]="['/address/' | relativeUrl, vin.prevout.scriptpubkey_address]" title="{{ vin.prevout.scriptpubkey_address }}">
<span class="d-block d-lg-none">{{ vin.prevout.scriptpubkey_address | shortenString : 16 }}</span>
<span class="d-none d-lg-block">{{ vin.prevout.scriptpubkey_address | shortenString : 42 }}</span>
</a>
@ -60,13 +67,16 @@
<tbody>
<tr *ngFor="let vout of getFilteredTxVout(tx); let vindex = index;">
<td>
<a *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/', vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
<a *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
<span class="d-block d-lg-none">{{ vout.scriptpubkey_address | shortenString : 16 }}</span>
<span class="d-none d-lg-block">{{ vout.scriptpubkey_address | shortenString : 42 }}</span>
</a>
<ng-template #scriptpubkey_type>
<ng-template [ngIf]="vout.pegout" [ngIfElse]="defaultscriptpubkey_type">
PEG OUT
Peg-out to <a [routerLink]="['/address/', vout.pegout.scriptpubkey_address]" title="{{ vout.pegout.scriptpubkey_address }}">
<span class="d-block d-lg-none">{{ vout.pegout.scriptpubkey_address | shortenString : 16 }}</span>
<span class="d-none d-lg-block">{{ vout.pegout.scriptpubkey_address | shortenString : 42 }}</span>
</a>
</ng-template>
<ng-template #defaultscriptpubkey_type>
{{ vout.scriptpubkey_type | scriptpubkeyType }}
@ -80,7 +90,7 @@
<br>
{{ assetsMinimal[vout.asset][0] }}
<br>
<a [routerLink]="['/asset/', vout.asset]">{{ vout.asset | shortenString : 13 }}</a>
<a [routerLink]="['/asset/' | relativeUrl, vout.asset]">{{ vout.asset | shortenString : 13 }}</a>
<br><br>
</div>
</ng-template>
@ -93,7 +103,7 @@
<ng-template #outspend>
<i *ngIf="!outspends[i][vindex] || !outspends[i][vindex].spent; else spent" class="arrow green"></i>
<ng-template #spent>
<a [routerLink]="['/tx/', outspends[i][vindex].txid]"><i class="arrow red"></i></a>
<a [routerLink]="['/tx/' | relativeUrl, outspends[i][vindex].txid]"><i class="arrow red"></i></a>
</ng-template>
</ng-template>
</td>

View File

@ -13,7 +13,7 @@ import { AssetsService } from 'src/app/services/assets.service';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TransactionsListComponent implements OnInit, OnChanges {
network = environment.network;
network = '';
nativeAssetId = environment.nativeAssetId;
@Input() transactions: Transaction[];
@ -35,6 +35,8 @@ export class TransactionsListComponent implements OnInit, OnChanges {
ngOnInit() {
this.latestBlock$ = this.stateService.blocks$;
this.stateService.networkChanged$.subscribe((network) => this.network = network);
if (this.network === 'liquid') {
this.assetsService.getAssetsMinimalJson$.subscribe((assets) => {
this.assetsMinimal = assets;

View File

@ -68,7 +68,7 @@ interface Pegout {
genesis_hash: string;
scriptpubkey: string;
scriptpubkey_asm: string;
scriptpubkey_addres: string;
scriptpubkey_address: string;
}
export interface Status {

View File

@ -0,0 +1,8 @@
import { RelativeUrlPipe } from './relative-url.pipe';
describe('RelativeUrlPipe', () => {
it('create an instance', () => {
const pipe = new RelativeUrlPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -0,0 +1,17 @@
import { Pipe, PipeTransform } from '@angular/core';
import { StateService } from 'src/app/services/state.service';
@Pipe({
name: 'relativeUrl'
})
export class RelativeUrlPipe implements PipeTransform {
constructor(
private stateService: StateService,
) { }
transform(value: string): string {
return (this.stateService.network ? '/' + this.stateService.network : '') + value;
}
}

View File

@ -2,43 +2,52 @@ import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
import { Observable } from 'rxjs';
import { StateService } from './state.service';
const API_BASE_URL = '/api/v1';
const API_BASE_URL = '/api{network}/v1';
@Injectable({
providedIn: 'root'
})
export class ApiService {
apiBaseUrl: string;
constructor(
private httpClient: HttpClient,
) { }
private stateService: StateService,
) {
this.apiBaseUrl = API_BASE_URL.replace('{network}', '');
this.stateService.networkChanged$.subscribe((network) => {
this.apiBaseUrl = API_BASE_URL.replace('{network}', network ? '/' + network : '');
});
}
list2HStatistics$(): Observable<OptimizedMempoolStats[]> {
return this.httpClient.get<OptimizedMempoolStats[]>(API_BASE_URL + '/statistics/2h');
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + '/statistics/2h');
}
list24HStatistics$(): Observable<OptimizedMempoolStats[]> {
return this.httpClient.get<OptimizedMempoolStats[]>(API_BASE_URL + '/statistics/24h');
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + '/statistics/24h');
}
list1WStatistics$(): Observable<OptimizedMempoolStats[]> {
return this.httpClient.get<OptimizedMempoolStats[]>(API_BASE_URL + '/statistics/1w');
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + '/statistics/1w');
}
list1MStatistics$(): Observable<OptimizedMempoolStats[]> {
return this.httpClient.get<OptimizedMempoolStats[]>(API_BASE_URL + '/statistics/1m');
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + '/statistics/1m');
}
list3MStatistics$(): Observable<OptimizedMempoolStats[]> {
return this.httpClient.get<OptimizedMempoolStats[]>(API_BASE_URL + '/statistics/3m');
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + '/statistics/3m');
}
list6MStatistics$(): Observable<OptimizedMempoolStats[]> {
return this.httpClient.get<OptimizedMempoolStats[]>(API_BASE_URL + '/statistics/6m');
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + '/statistics/6m');
}
list1YStatistics$(): Observable<OptimizedMempoolStats[]> {
return this.httpClient.get<OptimizedMempoolStats[]>(API_BASE_URL + '/statistics/1y');
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + '/statistics/1y');
}
getTransactionTimes$(txIds: string[]): Observable<number[]> {
@ -46,6 +55,6 @@ export class ApiService {
txIds.forEach((txId: string) => {
params = params.append('txId[]', txId);
});
return this.httpClient.get<number[]>(API_BASE_URL + '/transaction-times', { params });
return this.httpClient.get<number[]>(this.apiBaseUrl + '/transaction-times', { params });
}
}

View File

@ -1,15 +1,12 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ReplaySubject, Observable } from 'rxjs';
import { environment } from 'src/environments/environment';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class AssetsService {
network = environment.network;
getAssetsJson$: Observable<any>;
getAssetsMinimalJson$: Observable<any>;

View File

@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Block, Transaction, Address, Outspend, Recent, Asset } from '../interfaces/electrs.interface';
import { StateService } from './state.service';
const API_BASE_URL = document.location.protocol + '//' + document.location.hostname + ':' + document.location.port + '/electrs';
@ -9,61 +10,68 @@ const API_BASE_URL = document.location.protocol + '//' + document.location.hostn
providedIn: 'root'
})
export class ElectrsApiService {
apiBaseUrl: string;
constructor(
private httpClient: HttpClient,
private stateService: StateService,
) {
this.apiBaseUrl = API_BASE_URL;
this.stateService.networkChanged$.subscribe((network) => {
this.apiBaseUrl = API_BASE_URL + '/' + network;
});
}
getBlock$(hash: string): Observable<Block> {
return this.httpClient.get<Block>(API_BASE_URL + '/block/' + hash);
return this.httpClient.get<Block>(this.apiBaseUrl + '/block/' + hash);
}
listBlocks$(height?: number): Observable<Block[]> {
return this.httpClient.get<Block[]>(API_BASE_URL + '/blocks/' + (height || ''));
return this.httpClient.get<Block[]>(this.apiBaseUrl + '/blocks/' + (height || ''));
}
getTransaction$(txId: string): Observable<Transaction> {
return this.httpClient.get<Transaction>(API_BASE_URL + '/tx/' + txId);
return this.httpClient.get<Transaction>(this.apiBaseUrl + '/tx/' + txId);
}
getRecentTransaction$(): Observable<Recent[]> {
return this.httpClient.get<Recent[]>(API_BASE_URL + '/mempool/recent');
return this.httpClient.get<Recent[]>(this.apiBaseUrl + '/mempool/recent');
}
getOutspend$(hash: string, vout: number): Observable<Outspend> {
return this.httpClient.get<Outspend>(API_BASE_URL + '/tx/' + hash + '/outspend/' + vout);
return this.httpClient.get<Outspend>(this.apiBaseUrl + '/tx/' + hash + '/outspend/' + vout);
}
getOutspends$(hash: string): Observable<Outspend[]> {
return this.httpClient.get<Outspend[]>(API_BASE_URL + '/tx/' + hash + '/outspends');
return this.httpClient.get<Outspend[]>(this.apiBaseUrl + '/tx/' + hash + '/outspends');
}
getBlockTransactions$(hash: string, index: number = 0): Observable<Transaction[]> {
return this.httpClient.get<Transaction[]>(API_BASE_URL + '/block/' + hash + '/txs/' + index);
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + '/block/' + hash + '/txs/' + index);
}
getAddress$(address: string): Observable<Address> {
return this.httpClient.get<Address>(API_BASE_URL + '/address/' + address);
return this.httpClient.get<Address>(this.apiBaseUrl + '/address/' + address);
}
getAddressTransactions$(address: string): Observable<Transaction[]> {
return this.httpClient.get<Transaction[]>(API_BASE_URL + '/address/' + address + '/txs');
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + '/address/' + address + '/txs');
}
getAddressTransactionsFromHash$(address: string, txid: string): Observable<Transaction[]> {
return this.httpClient.get<Transaction[]>(API_BASE_URL + '/address/' + address + '/txs/chain/' + txid);
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + '/address/' + address + '/txs/chain/' + txid);
}
getAsset$(assetId: string): Observable<Asset> {
return this.httpClient.get<Asset>(API_BASE_URL + '/asset/' + assetId);
return this.httpClient.get<Asset>(this.apiBaseUrl + '/asset/' + assetId);
}
getAssetTransactions$(assetId: string): Observable<Transaction[]> {
return this.httpClient.get<Transaction[]>(API_BASE_URL + '/asset/' + assetId + '/txs');
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + '/asset/' + assetId + '/txs');
}
getAssetTransactionsFromHash$(assetId: string, txid: string): Observable<Transaction[]> {
return this.httpClient.get<Transaction[]>(API_BASE_URL + '/asset/' + assetId + '/txs/chain/' + txid);
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + '/asset/' + assetId + '/txs/chain/' + txid);
}
}

View File

@ -1,17 +1,20 @@
import { Injectable } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { environment } from 'src/environments/environment';
import { StateService } from './state.service';
@Injectable({
providedIn: 'root'
})
export class SeoService {
network = environment.network;
network = '';
defaultTitle = 'mempool - Bitcoin Explorer';
constructor(
private titleService: Title,
) { }
private stateService: StateService,
) {
this.stateService.networkChanged$.subscribe((network) => this.network = network);
}
setTitle(newTitle: string, prependNetwork = false) {
let networkName = '';

View File

@ -3,6 +3,7 @@ import { ReplaySubject, BehaviorSubject, Subject } from 'rxjs';
import { Block, Transaction } from '../interfaces/electrs.interface';
import { MempoolBlock, MemPoolState } from '../interfaces/websocket.interface';
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
import { Router, NavigationStart } from '@angular/router';
interface MarkBlockState {
blockHeight?: number;
@ -14,10 +15,13 @@ interface MarkBlockState {
providedIn: 'root'
})
export class StateService {
network = '';
latestBlockHeight = 0;
networkChanged$ = new ReplaySubject<string>(1);
blocks$ = new ReplaySubject<Block>(8);
conversions$ = new ReplaySubject<any>(1);
mempoolStats$ = new ReplaySubject<MemPoolState>();
mempoolStats$ = new ReplaySubject<MemPoolState>(1);
mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
txConfirmed$ = new Subject<Block>();
mempoolTransactions$ = new Subject<Transaction>();
@ -30,4 +34,34 @@ export class StateService {
connectionState$ = new BehaviorSubject<0 | 1 | 2>(2);
markBlock$ = new Subject<MarkBlockState>();
constructor(
private router: Router,
) {
this.router.events.subscribe((event) => {
if (event instanceof NavigationStart) {
switch (event.url.split('/')[1]) {
case 'liquid':
case 'liquid-tv':
if (this.network !== 'liquid') {
this.network = 'liquid';
this.networkChanged$.next('liquid');
}
return;
case 'testnet':
case 'testnet-tv':
if (this.network !== 'testnet') {
this.network = 'testnet';
this.networkChanged$.next('testnet');
}
return;
default:
if (this.network !== '') {
this.network = '';
this.networkChanged$.next('');
}
}
}
});
}
}

View File

@ -16,7 +16,7 @@ const EXPECT_PING_RESPONSE_AFTER_MS = 1000;
providedIn: 'root'
})
export class WebsocketService {
private websocketSubject: WebSocketSubject<WebsocketResponse> = webSocket<WebsocketResponse | any>(WEB_SOCKET_URL);
private websocketSubject: WebSocketSubject<WebsocketResponse>;
private goneOffline = false;
private lastWant: string[] | null = null;
private isTrackingTx = false;
@ -28,7 +28,22 @@ export class WebsocketService {
constructor(
private stateService: StateService,
) {
this.websocketSubject = webSocket<WebsocketResponse | any>(WEB_SOCKET_URL + '/' + this.stateService.network);
this.startSubscription();
this.stateService.networkChanged$.subscribe((network) => {
clearTimeout(this.onlineCheckTimeout);
clearTimeout(this.onlineCheckTimeoutTwo);
this.stateService.latestBlockHeight = 0;
this.websocketSubject.complete();
this.subscription.unsubscribe();
this.websocketSubject = webSocket<WebsocketResponse | any>(WEB_SOCKET_URL + '/' + network);
this.startSubscription();
});
}
startSubscription(retrying = false) {

View File

@ -1,9 +1,4 @@
const full = window.location.host;
const parts = full.split('.');
const sub = parts[0];
export const environment = {
production: true,
network: sub,
nativeAssetId: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d',
};

View File

@ -4,7 +4,6 @@
export const environment = {
production: false,
network: 'mainnet',
nativeAssetId: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d',
};

View File

@ -56,6 +56,14 @@ body {
}
}
.navbar-nav.liquid > .active {
background-color: #116761 !important;
}
.navbar-nav.testnet > .active {
background-color: #1d486f !important;
}
.form-control {
color: #495057;
}