Merge branch 'master' into add-py-ws-eg
This commit is contained in:
		
						commit
						666a03baf9
					
				@ -21,6 +21,7 @@ class NodesRoutes {
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/rankings/age', this.$getOldestNodes)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key/statistics', this.$getHistoricalNodeStats)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key', this.$getNode)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/group/:name', this.$getNodeGroup)
 | 
			
		||||
    ;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -33,6 +34,39 @@ class NodesRoutes {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $getNodeGroup(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      let nodesList;
 | 
			
		||||
      let nodes: any[] = [];
 | 
			
		||||
      switch (config.MEMPOOL.NETWORK) {
 | 
			
		||||
        case 'testnet':
 | 
			
		||||
          nodesList = ['032c7c7819276c4f706a04df1a0f1e10a5495994a7be4c1d3d28ca766e5a2b957b', '025a7e38c2834dd843591a4d23d5f09cdeb77ddca85f673c2d944a14220ff14cf7', '0395e2731a1673ef21d7a16a727c4fc4d4c35a861c428ce2c819c53d2b81c8bd55', '032ab2028c0b614c6d87824e2373529652fd7e4221b4c70cc4da7c7005c49afcf0', '029001b22fe70b48bee12d014df91982eb85ff1bd404ec772d5c83c4ee3e88d2c3', '0212e2848d79f928411da5f2ff0a8c95ec6ccb5a09d2031b6f71e91309dcde63af', '03e871a2229523d34f76e6311ff197cfe7f26c2fbec13554b93a46f4e710c47dab', '032202ec98d976b0e928bd1d91924e8bd3eab07231fc39feb3737b010071073df8', '02fa7c5a948d03d563a9f36940c2205a814e594d17c0042ced242c71a857d72605', '039c14fdec2d958e3d14cebf657451bbd9e039196615785e82c917f274e3fb2205', '033589bbcb233ffc416cefd5437c7f37e9d7cb7942d405e39e72c4c846d9b37f18', '029293110441c6e2eacb57e1255bf6ef05c41a6a676fe474922d33c19f98a7d584'];
 | 
			
		||||
          break;
 | 
			
		||||
        case 'signet':
 | 
			
		||||
          nodesList = ['03ddab321b760433cbf561b615ef62ac7d318630c5f51d523aaf5395b90b751956', '033d92c7bfd213ef1b34c90e985fb5dc77f9ec2409d391492484e57a44c4aca1de', '02ad010dda54253c1eb9efe38b0760657a3b43ecad62198c359c051c9d99d45781', '025196512905b8a3f1597428b867bec63ec9a95e5089eb7dc7e63e2d2691669029', '027c625aa1fbe3768db68ebcb05b53b6dc0ce68b7b54b8900d326d167363e684fe', '03f1629af3101fcc56b7aac2667016be84e3defbf3d0c8719f836c9b41c9a57a43', '02dfb81e2f7a3c4c9e8a51b70ef82b4a24549cc2fab1f5b2fd636501774a918991', '02d01ccf832944c68f10d39006093769c5b8bda886d561b128534e313d729fdb34', '02499ed23027d4698a6904ff4ec1b6085a61f10b9a6937f90438f9947e38e8ea86', '038310e3a786340f2bd7770704c7ccfe560fd163d9a1c99d67894597419d12cbf7', '03e5e9d879b72c7d67ecd483bae023bd33e695bb32b981a4021260f7b9d62bc761', '028d16e1a0ace4c0c0a421536d8d32ce484dfe6e2f726b7b0e7c30f12a195f8cc7'];
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          nodesList = ['03fbc17549ec667bccf397ababbcb4cdc0e3394345e4773079ab2774612ec9be61', '03da9a8623241ccf95f19cd645c6cecd4019ac91570e976eb0a128bebbc4d8a437', '03ca5340cf85cb2e7cf076e489f785410838de174e40be62723e8a60972ad75144', '0238bd27f02d67d6c51e269692bc8c9a32357a00e7777cba7f4f1f18a2a700b108', '03f983dcabed6baa1eab5b56c8b2e8fdc846ab3fd931155377897335e85a9fa57c', '03e399589533581e48796e29a825839a010036a61b20744fda929d6709fcbffcc5', '021f5288b5f72c42cd0d8801086af7ce09a816d8ee9a4c47a4b436399b26cb601a', '032b01b7585f781420cd4148841a82831ba37fa952342052cec16750852d4f2dd9', '02848036488d4b8fb1f1c4064261ec36151f43b085f0b51bd239ade3ddfc940c34', '02b6b1640fe029e304c216951af9fbefdb23b0bdc9baaf327540d31b6107841fdf', '03694289827203a5b3156d753071ddd5bf92e371f5a462943f9555eef6d2d6606c', '0283d850db7c3e8ea7cc9c4abc7afaab12bbdf72b677dcba1d608350d2537d7d43'];
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (let pubKey of nodesList) {
 | 
			
		||||
        try {
 | 
			
		||||
          const node = await nodesApi.$getNode(pubKey);
 | 
			
		||||
          if (node) {
 | 
			
		||||
            nodes.push(node);
 | 
			
		||||
          }
 | 
			
		||||
        } catch (e) {}
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      res.header('Pragma', 'public');
 | 
			
		||||
      res.header('Cache-control', 'public');
 | 
			
		||||
      res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
			
		||||
      res.json(nodes);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).send(e instanceof Error ? e.message : e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $getNode(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const node = await nodesApi.$getNode(req.params.public_key);
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,8 @@ class StatisticsApi {
 | 
			
		||||
  public async $getStatistics(interval: string | null = null): Promise<any> {
 | 
			
		||||
    interval = Common.getSqlInterval(interval);
 | 
			
		||||
 | 
			
		||||
    let query = `SELECT UNIX_TIMESTAMP(added) AS added, channel_count, total_capacity, tor_nodes, clearnet_nodes, unannounced_nodes
 | 
			
		||||
    let query = `SELECT UNIX_TIMESTAMP(added) AS added, channel_count, total_capacity,
 | 
			
		||||
      tor_nodes, clearnet_nodes, unannounced_nodes, clearnet_tor_nodes
 | 
			
		||||
      FROM lightning_stats`;
 | 
			
		||||
 | 
			
		||||
    if (interval) {
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser';
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { ModuleWithProviders, NgModule } from '@angular/core';
 | 
			
		||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
 | 
			
		||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 | 
			
		||||
import { AppRoutingModule } from './app-routing.module';
 | 
			
		||||
@ -20,6 +20,23 @@ import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-st
 | 
			
		||||
import { CapAddressPipe } from './shared/pipes/cap-address-pipe/cap-address-pipe';
 | 
			
		||||
import { AppPreloadingStrategy } from './app.preloading-strategy';
 | 
			
		||||
 | 
			
		||||
const providers = [
 | 
			
		||||
  ElectrsApiService,
 | 
			
		||||
  StateService,
 | 
			
		||||
  WebsocketService,
 | 
			
		||||
  AudioService,
 | 
			
		||||
  SeoService,
 | 
			
		||||
  OpenGraphService,
 | 
			
		||||
  StorageService,
 | 
			
		||||
  EnterpriseService,
 | 
			
		||||
  LanguageService,
 | 
			
		||||
  ShortenStringPipe,
 | 
			
		||||
  FiatShortenerPipe,
 | 
			
		||||
  CapAddressPipe,
 | 
			
		||||
  AppPreloadingStrategy,
 | 
			
		||||
  { provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
    AppComponent,
 | 
			
		||||
@ -32,22 +49,17 @@ import { AppPreloadingStrategy } from './app.preloading-strategy';
 | 
			
		||||
    BrowserAnimationsModule,
 | 
			
		||||
    SharedModule,
 | 
			
		||||
  ],
 | 
			
		||||
  providers: [
 | 
			
		||||
    ElectrsApiService,
 | 
			
		||||
    StateService,
 | 
			
		||||
    WebsocketService,
 | 
			
		||||
    AudioService,
 | 
			
		||||
    SeoService,
 | 
			
		||||
    OpenGraphService,
 | 
			
		||||
    StorageService,
 | 
			
		||||
    EnterpriseService,
 | 
			
		||||
    LanguageService,
 | 
			
		||||
    ShortenStringPipe,
 | 
			
		||||
    FiatShortenerPipe,
 | 
			
		||||
    CapAddressPipe,
 | 
			
		||||
    AppPreloadingStrategy,
 | 
			
		||||
    { provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true }
 | 
			
		||||
  ],
 | 
			
		||||
  providers: providers,
 | 
			
		||||
  bootstrap: [AppComponent]
 | 
			
		||||
})
 | 
			
		||||
export class AppModule { }
 | 
			
		||||
 | 
			
		||||
@NgModule({})
 | 
			
		||||
export class MempoolSharedModule{
 | 
			
		||||
  static forRoot(): ModuleWithProviders<MempoolSharedModule> {
 | 
			
		||||
    return {
 | 
			
		||||
      ngModule: AppModule,
 | 
			
		||||
      providers: providers
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,11 @@
 | 
			
		||||
import { Component, OnInit, OnDestroy } from '@angular/core';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { switchMap, filter, catchError } from 'rxjs/operators';
 | 
			
		||||
import { ParamMap, ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { Subscription, of } from 'rxjs';
 | 
			
		||||
import { BisqTransaction } from '../bisq.interfaces';
 | 
			
		||||
import { BisqApiService } from '../bisq-api.service';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-bisq-address',
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,14 @@
 | 
			
		||||
import { Component, OnInit, OnDestroy } from '@angular/core';
 | 
			
		||||
import { BisqBlock } from 'src/app/bisq/bisq.interfaces';
 | 
			
		||||
import { BisqBlock } from '../../bisq/bisq.interfaces';
 | 
			
		||||
import { Location } from '@angular/common';
 | 
			
		||||
import { BisqApiService } from '../bisq-api.service';
 | 
			
		||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
 | 
			
		||||
import { Subscription, of } from 'rxjs';
 | 
			
		||||
import { switchMap, catchError } from 'rxjs/operators';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
import { HttpErrorResponse } from '@angular/common/http';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-bisq-block',
 | 
			
		||||
 | 
			
		||||
@ -3,9 +3,9 @@ import { BisqApiService } from '../bisq-api.service';
 | 
			
		||||
import { switchMap, map, take, mergeMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { BisqBlock, BisqOutput, BisqTransaction } from '../bisq.interfaces';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { ActivatedRoute, Router } from '@angular/router';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-bisq-blocks',
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 | 
			
		||||
import { Observable, combineLatest, BehaviorSubject, of } from 'rxjs';
 | 
			
		||||
import { map, share, switchMap } from 'rxjs/operators';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
import { BisqApiService } from '../bisq-api.service';
 | 
			
		||||
import { Trade } from '../bisq.interfaces';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 | 
			
		||||
import { Observable, combineLatest, BehaviorSubject, of } from 'rxjs';
 | 
			
		||||
import { map, share, switchMap } from 'rxjs/operators';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
import { BisqApiService } from '../bisq-api.service';
 | 
			
		||||
import { Trade } from '../bisq.interfaces';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,8 +3,8 @@ import { FormBuilder, FormGroup } from '@angular/forms';
 | 
			
		||||
import { ActivatedRoute, Router } from '@angular/router';
 | 
			
		||||
import { combineLatest, merge, Observable, of } from 'rxjs';
 | 
			
		||||
import { map, switchMap } from 'rxjs/operators';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
import { BisqApiService } from '../bisq-api.service';
 | 
			
		||||
import { OffersMarket, Trade } from '../bisq.interfaces';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { BisqApiService } from '../bisq-api.service';
 | 
			
		||||
import { BisqStats } from '../bisq.interfaces';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-bisq-stats',
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { Component, ChangeDetectionStrategy, Input, OnChanges } from '@angular/core';
 | 
			
		||||
import { BisqTransaction } from 'src/app/bisq/bisq.interfaces';
 | 
			
		||||
import { BisqTransaction } from '../../bisq/bisq.interfaces';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-bisq-transaction-details',
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,15 @@
 | 
			
		||||
import { Component, OnInit, OnDestroy } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
 | 
			
		||||
import { BisqTransaction } from 'src/app/bisq/bisq.interfaces';
 | 
			
		||||
import { BisqTransaction } from '../../bisq/bisq.interfaces';
 | 
			
		||||
import { switchMap, map, catchError } from 'rxjs/operators';
 | 
			
		||||
import { of, Observable, Subscription } from 'rxjs';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { Block, Transaction } from 'src/app/interfaces/electrs.interface';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { Block, Transaction } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { BisqApiService } from '../bisq-api.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
import { HttpErrorResponse } from '@angular/common/http';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-bisq-transaction',
 | 
			
		||||
 | 
			
		||||
@ -4,11 +4,11 @@ import { BisqTransaction, BisqOutput } from '../bisq.interfaces';
 | 
			
		||||
import { Observable, Subscription } from 'rxjs';
 | 
			
		||||
import { switchMap, map, tap } from 'rxjs/operators';
 | 
			
		||||
import { BisqApiService } from '../bisq-api.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { FormGroup, FormBuilder } from '@angular/forms';
 | 
			
		||||
import { Router, ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'src/app/components/ngx-bootstrap-multiselect/types'
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from '../../components/ngx-bootstrap-multiselect/types'
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-bisq-transactions',
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
import { Component, OnInit, ChangeDetectionStrategy, Input, OnChanges } from '@angular/core';
 | 
			
		||||
import { BisqTransaction } from 'src/app/bisq/bisq.interfaces';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { BisqTransaction } from '../../bisq/bisq.interfaces';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { map } from 'rxjs/operators';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { Block } from 'src/app/interfaces/electrs.interface';
 | 
			
		||||
import { Block } from '../../interfaces/electrs.interface';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-bisq-transfers',
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,13 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@angular/core';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { IBackendInfo } from 'src/app/interfaces/websocket.interface';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { IBackendInfo } from '../../interfaces/websocket.interface';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
import { map } from 'rxjs/operators';
 | 
			
		||||
import { ITranslators } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { ITranslators } from '../../interfaces/node-api.interface';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-about',
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,16 @@
 | 
			
		||||
<a *ngIf="channel; else default" [routerLink]="['/lightning/channel' | relativeUrl, channel.id]">
 | 
			
		||||
  <span
 | 
			
		||||
    *ngIf="label"
 | 
			
		||||
    class="badge badge-pill badge-warning"
 | 
			
		||||
  >{{ label }}</span>
 | 
			
		||||
</a>
 | 
			
		||||
<ng-template [ngIf]="channel" [ngIfElse]="default">
 | 
			
		||||
  <div>
 | 
			
		||||
    <div class="badge-positioner">
 | 
			
		||||
      <a [routerLink]="['/lightning/channel' | relativeUrl, channel.id]">
 | 
			
		||||
        <span 
 | 
			
		||||
          *ngIf="label"
 | 
			
		||||
          class="badge badge-pill badge-warning"
 | 
			
		||||
        >{{ label }}</span>
 | 
			
		||||
      </a>
 | 
			
		||||
    </div>
 | 
			
		||||
     
 | 
			
		||||
  </div>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<ng-template #default>
 | 
			
		||||
  <span
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,7 @@
 | 
			
		||||
.badge {
 | 
			
		||||
  margin-right: 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.badge-positioner {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { Component, ChangeDetectionStrategy, Input, OnChanges } from '@angular/core';
 | 
			
		||||
import { Vin, Vout } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { parseMultisigScript } from 'src/app/bitcoin.utils';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { parseMultisigScript } from '../../bitcoin.utils';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-address-labels',
 | 
			
		||||
 | 
			
		||||
@ -3,13 +3,13 @@ import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
import { switchMap, filter, catchError, map, tap } from 'rxjs/operators';
 | 
			
		||||
import { Address, Transaction } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { OpenGraphService } from 'src/app/services/opengraph.service';
 | 
			
		||||
import { AudioService } from 'src/app/services/audio.service';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { OpenGraphService } from '../../services/opengraph.service';
 | 
			
		||||
import { AudioService } from '../../services/audio.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { of, merge, Subscription, Observable } from 'rxjs';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { AddressInformation } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { AddressInformation } from '../../interfaces/node-api.interface';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-address-preview',
 | 
			
		||||
 | 
			
		||||
@ -3,13 +3,13 @@ import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
import { switchMap, filter, catchError, map, tap } from 'rxjs/operators';
 | 
			
		||||
import { Address, Transaction } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { AudioService } from 'src/app/services/audio.service';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { AudioService } from '../../services/audio.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { of, merge, Subscription, Observable } from 'rxjs';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { AddressInformation } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { AddressInformation } from '../../interfaces/node-api.interface';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-address',
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { Location } from '@angular/common';
 | 
			
		||||
import { Component, HostListener, OnInit, Inject, LOCALE_ID, HostBinding } from '@angular/core';
 | 
			
		||||
import { Router, NavigationEnd } from '@angular/router';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { OpenGraphService } from 'src/app/services/opengraph.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { OpenGraphService } from '../../services/opengraph.service';
 | 
			
		||||
import { NgbTooltipConfig } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
 | 
			
		||||
import { combineLatest, Observable } from 'rxjs';
 | 
			
		||||
import { map } from 'rxjs/operators';
 | 
			
		||||
import { moveDec } from 'src/app/bitcoin.utils';
 | 
			
		||||
import { AssetsService } from 'src/app/services/assets.service';
 | 
			
		||||
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
 | 
			
		||||
import { moveDec } from '../../bitcoin.utils';
 | 
			
		||||
import { AssetsService } from '../../services/assets.service';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
import { environment } from 'src/environments/environment';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
 | 
			
		||||
@ -3,15 +3,15 @@ import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
import { switchMap, filter, catchError, take } from 'rxjs/operators';
 | 
			
		||||
import { Asset, Transaction } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { AudioService } from 'src/app/services/audio.service';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { AudioService } from '../../services/audio.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { of, merge, Subscription, combineLatest } from 'rxjs';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { environment } from 'src/environments/environment';
 | 
			
		||||
import { AssetsService } from 'src/app/services/assets.service';
 | 
			
		||||
import { moveDec } from 'src/app/bitcoin.utils';
 | 
			
		||||
import { AssetsService } from '../../services/assets.service';
 | 
			
		||||
import { moveDec } from '../../bitcoin.utils';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-asset',
 | 
			
		||||
 | 
			
		||||
@ -2,8 +2,8 @@ import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
			
		||||
import { combineLatest, Observable } from 'rxjs';
 | 
			
		||||
import { map, switchMap } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { AssetsService } from 'src/app/services/assets.service';
 | 
			
		||||
import { ApiService } from '../../../services/api.service';
 | 
			
		||||
import { AssetsService } from '../../../services/assets.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-asset-group',
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { ApiService } from '../../../services/api.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-assets-featured',
 | 
			
		||||
 | 
			
		||||
@ -4,11 +4,11 @@ import { Router } from '@angular/router';
 | 
			
		||||
import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { merge, Observable, of, Subject } from 'rxjs';
 | 
			
		||||
import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators';
 | 
			
		||||
import { AssetExtended } from 'src/app/interfaces/electrs.interface';
 | 
			
		||||
import { AssetsService } from 'src/app/services/assets.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { AssetExtended } from '../../../interfaces/electrs.interface';
 | 
			
		||||
import { AssetsService } from '../../../services/assets.service';
 | 
			
		||||
import { SeoService } from '../../../services/seo.service';
 | 
			
		||||
import { StateService } from '../../../services/state.service';
 | 
			
		||||
import { RelativeUrlPipe } from '../../../shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { environment } from 'src/environments/environment';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,13 @@
 | 
			
		||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
 | 
			
		||||
import { AssetsService } from 'src/app/services/assets.service';
 | 
			
		||||
import { AssetsService } from '../../services/assets.service';
 | 
			
		||||
import { environment } from 'src/environments/environment';
 | 
			
		||||
import { FormGroup } from '@angular/forms';
 | 
			
		||||
import { filter, map, switchMap, take } from 'rxjs/operators';
 | 
			
		||||
import { ActivatedRoute, Router } from '@angular/router';
 | 
			
		||||
import { combineLatest, Observable } from 'rxjs';
 | 
			
		||||
import { AssetExtended } from 'src/app/interfaces/electrs.interface';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { AssetExtended } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-assets',
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { Env, StateService } from '../../services/state.service';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { LanguageService } from 'src/app/services/language.service';
 | 
			
		||||
import { EnterpriseService } from 'src/app/services/enterprise.service';
 | 
			
		||||
import { LanguageService } from '../../services/language.service';
 | 
			
		||||
import { EnterpriseService } from '../../services/enterprise.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-bisq-master-page',
 | 
			
		||||
 | 
			
		||||
@ -2,11 +2,11 @@ import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { map, share, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { BlockAudit, TransactionStripped } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { detectWebGL } from 'src/app/shared/graphs.utils';
 | 
			
		||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { BlockAudit, TransactionStripped } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { detectWebGL } from '../../shared/graphs.utils';
 | 
			
		||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { BlockOverviewGraphComponent } from '../block-overview-graph/block-overview-graph.component';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
 | 
			
		||||
@ -2,16 +2,16 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, O
 | 
			
		||||
import { EChartsOption } from 'echarts';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { formatNumber } from '@angular/common';
 | 
			
		||||
import { FormBuilder, FormGroup } from '@angular/forms';
 | 
			
		||||
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from 'src/app/shared/graphs.utils';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { MiningService } from 'src/app/services/mining.service';
 | 
			
		||||
import { selectPowerOfTen } from 'src/app/bitcoin.utils';
 | 
			
		||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from '../../shared/graphs.utils';
 | 
			
		||||
import { StorageService } from '../../services/storage.service';
 | 
			
		||||
import { MiningService } from '../../services/mining.service';
 | 
			
		||||
import { selectPowerOfTen } from '../../bitcoin.utils';
 | 
			
		||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { ActivatedRoute, Router } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
 | 
			
		||||
@ -2,15 +2,15 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit }
 | 
			
		||||
import { EChartsOption, graphic } from 'echarts';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { formatCurrency, formatNumber, getCurrencySymbol } from '@angular/common';
 | 
			
		||||
import { FormBuilder, FormGroup } from '@angular/forms';
 | 
			
		||||
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from 'src/app/shared/graphs.utils';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { MiningService } from 'src/app/services/mining.service';
 | 
			
		||||
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from '../../shared/graphs.utils';
 | 
			
		||||
import { StorageService } from '../../services/storage.service';
 | 
			
		||||
import { MiningService } from '../../services/mining.service';
 | 
			
		||||
import { ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { FiatShortenerPipe } from 'src/app/shared/pipes/fiat-shortener.pipe';
 | 
			
		||||
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-block-fees-graph',
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { Component, ElementRef, ViewChild, HostListener, Input, Output, EventEmitter, NgZone, AfterViewInit, OnDestroy } from '@angular/core';
 | 
			
		||||
import { TransactionStripped } from 'src/app/interfaces/websocket.interface';
 | 
			
		||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
 | 
			
		||||
import { FastVertexArray } from './fast-vertex-array';
 | 
			
		||||
import BlockScene from './block-scene';
 | 
			
		||||
import TxSprite from './tx-sprite';
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { FastVertexArray } from './fast-vertex-array';
 | 
			
		||||
import TxView from './tx-view';
 | 
			
		||||
import { TransactionStripped } from 'src/app/interfaces/websocket.interface';
 | 
			
		||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
 | 
			
		||||
import { Position, Square, ViewUpdateParams } from './sprite-types';
 | 
			
		||||
 | 
			
		||||
export default class BlockScene {
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import TxSprite from './tx-sprite';
 | 
			
		||||
import { FastVertexArray } from './fast-vertex-array';
 | 
			
		||||
import { TransactionStripped } from 'src/app/interfaces/websocket.interface';
 | 
			
		||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
 | 
			
		||||
import { SpriteUpdateParams, Square, Color, ViewUpdateParams } from './sprite-types';
 | 
			
		||||
import { feeLevels, mempoolFeeColors } from 'src/app/app.constants';
 | 
			
		||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
 | 
			
		||||
 | 
			
		||||
const hoverTransitionTime = 300;
 | 
			
		||||
const defaultHoverColor = hexToColor('1bd8f4');
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Component, ElementRef, ViewChild, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core';
 | 
			
		||||
import { TransactionStripped } from 'src/app/interfaces/websocket.interface';
 | 
			
		||||
import { Position } from 'src/app/components/block-overview-graph/sprite-types.js';
 | 
			
		||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
 | 
			
		||||
import { Position } from '../../components/block-overview-graph/sprite-types.js';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-block-overview-tooltip',
 | 
			
		||||
 | 
			
		||||
@ -2,15 +2,15 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, O
 | 
			
		||||
import { EChartsOption } from 'echarts';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { formatNumber } from '@angular/common';
 | 
			
		||||
import { FormBuilder, FormGroup } from '@angular/forms';
 | 
			
		||||
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from 'src/app/shared/graphs.utils';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from '../../shared/graphs.utils';
 | 
			
		||||
import { StorageService } from '../../services/storage.service';
 | 
			
		||||
import { ActivatedRoute, Router } from '@angular/router';
 | 
			
		||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-block-prediction-graph',
 | 
			
		||||
 | 
			
		||||
@ -2,15 +2,15 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit }
 | 
			
		||||
import { EChartsOption, graphic } from 'echarts';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { formatCurrency, formatNumber, getCurrencySymbol } from '@angular/common';
 | 
			
		||||
import { FormBuilder, FormGroup } from '@angular/forms';
 | 
			
		||||
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from 'src/app/shared/graphs.utils';
 | 
			
		||||
import { MiningService } from 'src/app/services/mining.service';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from '../../shared/graphs.utils';
 | 
			
		||||
import { MiningService } from '../../services/mining.service';
 | 
			
		||||
import { StorageService } from '../../services/storage.service';
 | 
			
		||||
import { ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { FiatShortenerPipe } from 'src/app/shared/pipes/fiat-shortener.pipe';
 | 
			
		||||
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-block-rewards-graph',
 | 
			
		||||
 | 
			
		||||
@ -2,14 +2,14 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit, H
 | 
			
		||||
import { EChartsOption} from 'echarts';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { formatNumber } from '@angular/common';
 | 
			
		||||
import { FormBuilder, FormGroup } from '@angular/forms';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { MiningService } from 'src/app/services/mining.service';
 | 
			
		||||
import { StorageService } from '../../services/storage.service';
 | 
			
		||||
import { MiningService } from '../../services/mining.service';
 | 
			
		||||
import { ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { download, formatterXAxis } from 'src/app/shared/graphs.utils';
 | 
			
		||||
import { download, formatterXAxis } from '../../shared/graphs.utils';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-block-sizes-weights-graph',
 | 
			
		||||
 | 
			
		||||
@ -4,11 +4,11 @@ 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 'src/app/services/seo.service';
 | 
			
		||||
import { OpenGraphService } from 'src/app/services/opengraph.service';
 | 
			
		||||
import { BlockExtended, TransactionStripped } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { BlockOverviewGraphComponent } from 'src/app/components/block-overview-graph/block-overview-graph.component';
 | 
			
		||||
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 { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-block-preview',
 | 
			
		||||
 | 
			
		||||
@ -6,13 +6,13 @@ import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith,
 | 
			
		||||
import { Transaction, Vout } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { Observable, of, Subscription, asyncScheduler, EMPTY } from 'rxjs';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { BlockExtended, TransactionStripped } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { BlockOverviewGraphComponent } from 'src/app/components/block-overview-graph/block-overview-graph.component';
 | 
			
		||||
import { detectWebGL } from 'src/app/shared/graphs.utils';
 | 
			
		||||
import { SeoService } from '../../services/seo.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 '../../components/block-overview-graph/block-overview-graph.component';
 | 
			
		||||
import { detectWebGL } from '../../shared/graphs.utils';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-block',
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
 | 
			
		||||
import { Observable, Subscription } from 'rxjs';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { specialBlocks } from 'src/app/app.constants';
 | 
			
		||||
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { specialBlocks } from '../../app.constants';
 | 
			
		||||
import { BlockExtended } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { Location } from '@angular/common';
 | 
			
		||||
import { config } from 'process';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-blockchain',
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
 | 
			
		||||
import { BehaviorSubject, combineLatest, concat, Observable, timer } from 'rxjs';
 | 
			
		||||
import { delayWhen, map, retryWhen, scan, skip, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { BlockExtended } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-blocks-list',
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,15 @@
 | 
			
		||||
<span #buttonWrapper [attr.data-tlite]="copiedMessage" style="position: relative;">
 | 
			
		||||
  <button #btn class="btn btn-sm btn-link pt-0 {{ leftPadding ? 'padding' : '' }}" [attr.data-clipboard-text]="text"> 
 | 
			
		||||
    <app-svg-images name="clippy" [width]="size === 'small' ? '10' : '13'" viewBox="0 0 1000 1000"></app-svg-images>
 | 
			
		||||
<ng-template [ngIf]="button" [ngIfElse]="btnLink">
 | 
			
		||||
  <button #btn [attr.data-clipboard-text]="text" [class]="class" type="button" [disabled]="text === ''">
 | 
			
		||||
    <span #buttonWrapper [attr.data-tlite]="copiedMessage" style="position: relative;top: -2px;left: 1px;">
 | 
			
		||||
      <app-svg-images name="clippy" [width]="size === 'small' ? '10' : '13'" viewBox="0 0 1000 1000"></app-svg-images>
 | 
			
		||||
    </span>
 | 
			
		||||
  </button>
 | 
			
		||||
</span>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<ng-template #btnLink>
 | 
			
		||||
  <span #buttonWrapper [attr.data-tlite]="copiedMessage" style="position: relative;">
 | 
			
		||||
    <button #btn class="btn btn-sm btn-link pt-0 {{ leftPadding ? 'padding' : '' }}" [attr.data-clipboard-text]="text"> 
 | 
			
		||||
      <app-svg-images name="clippy" [width]="size === 'small' ? '10' : '13'" viewBox="0 0 1000 1000"></app-svg-images>
 | 
			
		||||
    </button>
 | 
			
		||||
  </span>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,8 @@ import * as tlite from 'tlite';
 | 
			
		||||
export class ClipboardComponent implements AfterViewInit {
 | 
			
		||||
  @ViewChild('btn') btn: ElementRef;
 | 
			
		||||
  @ViewChild('buttonWrapper') buttonWrapper: ElementRef;
 | 
			
		||||
  @Input() button = false;
 | 
			
		||||
  @Input() class = 'btn btn-secondary ml-1';
 | 
			
		||||
  @Input() size: 'small' | 'normal' = 'normal';
 | 
			
		||||
  @Input() text: string;
 | 
			
		||||
  @Input() leftPadding = true;
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
import { Component, Inject, LOCALE_ID, OnInit } from '@angular/core';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { map } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { formatNumber } from '@angular/common';
 | 
			
		||||
import { selectPowerOfTen } from 'src/app/bitcoin.utils';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { selectPowerOfTen } from '../../bitcoin.utils';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-difficulty-adjustments-table',
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { Recommendedfees } from 'src/app/interfaces/websocket.interface';
 | 
			
		||||
import { feeLevels, mempoolFeeColors } from 'src/app/app.constants';
 | 
			
		||||
import { Recommendedfees } from '../../interfaces/websocket.interface';
 | 
			
		||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
 | 
			
		||||
import { tap } from 'rxjs/operators';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { Observable, combineLatest } from 'rxjs';
 | 
			
		||||
import { map } from 'rxjs/operators';
 | 
			
		||||
import { MempoolInfo } from 'src/app/interfaces/websocket.interface';
 | 
			
		||||
import { MempoolInfo } from '../../interfaces/websocket.interface';
 | 
			
		||||
 | 
			
		||||
interface MempoolBlocksData {
 | 
			
		||||
  blocks: number;
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Component, OnInit } from "@angular/core";
 | 
			
		||||
import { StateService } from "src/app/services/state.service";
 | 
			
		||||
import { WebsocketService } from "src/app/services/websocket.service";
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-graphs',
 | 
			
		||||
 | 
			
		||||
@ -2,16 +2,16 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit, H
 | 
			
		||||
import { EChartsOption, graphic } from 'echarts';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { formatNumber } from '@angular/common';
 | 
			
		||||
import { FormBuilder, FormGroup } from '@angular/forms';
 | 
			
		||||
import { selectPowerOfTen } from 'src/app/bitcoin.utils';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { MiningService } from 'src/app/services/mining.service';
 | 
			
		||||
import { download } from 'src/app/shared/graphs.utils';
 | 
			
		||||
import { selectPowerOfTen } from '../../bitcoin.utils';
 | 
			
		||||
import { StorageService } from '../../services/storage.service';
 | 
			
		||||
import { MiningService } from '../../services/mining.service';
 | 
			
		||||
import { download } from '../../shared/graphs.utils';
 | 
			
		||||
import { ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-hashrate-chart',
 | 
			
		||||
 | 
			
		||||
@ -2,13 +2,13 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, L
 | 
			
		||||
import { EChartsOption } from 'echarts';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { delay, map, retryWhen, share, startWith, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { FormBuilder, FormGroup } from '@angular/forms';
 | 
			
		||||
import { poolsColor } from 'src/app/app.constants';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { MiningService } from 'src/app/services/mining.service';
 | 
			
		||||
import { download } from 'src/app/shared/graphs.utils';
 | 
			
		||||
import { poolsColor } from '../../app.constants';
 | 
			
		||||
import { StorageService } from '../../services/storage.service';
 | 
			
		||||
import { MiningService } from '../../services/mining.service';
 | 
			
		||||
import { download } from '../../shared/graphs.utils';
 | 
			
		||||
import { ActivatedRoute } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { Component, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnInit } from '@angular/core';
 | 
			
		||||
import { EChartsOption } from 'echarts';
 | 
			
		||||
import { OnChanges } from '@angular/core';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { download, formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
 | 
			
		||||
import { StorageService } from '../../services/storage.service';
 | 
			
		||||
import { download, formatterXAxis, formatterXAxisLabel } from '../../shared/graphs.utils';
 | 
			
		||||
import { formatNumber } from '@angular/common';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { DOCUMENT } from '@angular/common';
 | 
			
		||||
import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core';
 | 
			
		||||
import { FormBuilder, FormGroup } from '@angular/forms';
 | 
			
		||||
import { languages } from 'src/app/app.constants';
 | 
			
		||||
import { LanguageService } from 'src/app/services/language.service';
 | 
			
		||||
import { languages } from '../../app.constants';
 | 
			
		||||
import { LanguageService } from '../../services/language.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-language-selector',
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { Env, StateService } from '../../services/state.service';
 | 
			
		||||
import { merge, Observable, of} from 'rxjs';
 | 
			
		||||
import { LanguageService } from 'src/app/services/language.service';
 | 
			
		||||
import { EnterpriseService } from 'src/app/services/enterprise.service';
 | 
			
		||||
import { LanguageService } from '../../services/language.service';
 | 
			
		||||
import { EnterpriseService } from '../../services/enterprise.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-liquid-master-page',
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { map } from 'rxjs/operators';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-loading-indicator',
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { Observable, merge, of } from 'rxjs';
 | 
			
		||||
import { LanguageService } from 'src/app/services/language.service';
 | 
			
		||||
import { LanguageService } from '../../services/language.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-master-page-preview',
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { Component, Inject, OnInit } from '@angular/core';
 | 
			
		||||
import { Env, StateService } from '../../services/state.service';
 | 
			
		||||
import { Observable, merge, of } from 'rxjs';
 | 
			
		||||
import { LanguageService } from 'src/app/services/language.service';
 | 
			
		||||
import { EnterpriseService } from 'src/app/services/enterprise.service';
 | 
			
		||||
import { LanguageService } from '../../services/language.service';
 | 
			
		||||
import { EnterpriseService } from '../../services/enterprise.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-master-page',
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,12 @@
 | 
			
		||||
import { Component, ComponentRef, ViewChild, HostListener, Input, Output, EventEmitter,
 | 
			
		||||
  OnDestroy, OnChanges, ChangeDetectionStrategy, AfterViewInit } from '@angular/core';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { MempoolBlockDelta, TransactionStripped } from 'src/app/interfaces/websocket.interface';
 | 
			
		||||
import { BlockOverviewGraphComponent } from 'src/app/components/block-overview-graph/block-overview-graph.component';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { MempoolBlockDelta, TransactionStripped } from '../../interfaces/websocket.interface';
 | 
			
		||||
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
 | 
			
		||||
import { Subscription, BehaviorSubject, merge, of } from 'rxjs';
 | 
			
		||||
import { switchMap, filter } from 'rxjs/operators';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,11 @@
 | 
			
		||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
			
		||||
import { switchMap, map, tap, filter } from 'rxjs/operators';
 | 
			
		||||
import { MempoolBlock, TransactionStripped } from 'src/app/interfaces/websocket.interface';
 | 
			
		||||
import { MempoolBlock, TransactionStripped } from '../../interfaces/websocket.interface';
 | 
			
		||||
import { Observable, BehaviorSubject } from 'rxjs';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-mempool-block',
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,14 @@
 | 
			
		||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, Input } from '@angular/core';
 | 
			
		||||
import { Subscription, Observable, fromEvent, merge, of, combineLatest, timer } from 'rxjs';
 | 
			
		||||
import { MempoolBlock } from 'src/app/interfaces/websocket.interface';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { MempoolBlock } from '../../interfaces/websocket.interface';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
import { take, map, switchMap } from 'rxjs/operators';
 | 
			
		||||
import { feeLevels, mempoolFeeColors } from 'src/app/app.constants';
 | 
			
		||||
import { specialBlocks } from 'src/app/app.constants';
 | 
			
		||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
 | 
			
		||||
import { specialBlocks } from '../../app.constants';
 | 
			
		||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { Location } from '@angular/common';
 | 
			
		||||
import { DifficultyAdjustment } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { DifficultyAdjustment } from '../../interfaces/node-api.interface';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-mempool-blocks',
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,12 @@
 | 
			
		||||
import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core';
 | 
			
		||||
import { VbytesPipe } from 'src/app/shared/pipes/bytes-pipe/vbytes.pipe';
 | 
			
		||||
import { VbytesPipe } from '../../shared/pipes/bytes-pipe/vbytes.pipe';
 | 
			
		||||
import { formatNumber } from '@angular/common';
 | 
			
		||||
import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { OptimizedMempoolStats } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { StorageService } from '../../services/storage.service';
 | 
			
		||||
import { EChartsOption } from 'echarts';
 | 
			
		||||
import { feeLevels, chartColors } from 'src/app/app.constants';
 | 
			
		||||
import { download, formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
 | 
			
		||||
import { feeLevels, chartColors } from '../../app.constants';
 | 
			
		||||
import { download, formatterXAxis, formatterXAxisLabel } from '../../shared/graphs.utils';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-mempool-graph',
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-mining-dashboard',
 | 
			
		||||
 | 
			
		||||
@ -4,15 +4,15 @@ import { ActivatedRoute, Router } from '@angular/router';
 | 
			
		||||
import { EChartsOption, PieSeriesOption } from 'echarts';
 | 
			
		||||
import { concat, Observable } from 'rxjs';
 | 
			
		||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { SinglePoolStats } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { SinglePoolStats } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { StorageService } from '../..//services/storage.service';
 | 
			
		||||
import { MiningService, MiningStats } from '../../services/mining.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { chartColors, poolsColor } from 'src/app/app.constants';
 | 
			
		||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { download } from 'src/app/shared/graphs.utils';
 | 
			
		||||
import { isMobile } from 'src/app/shared/common.utils';
 | 
			
		||||
import { chartColors, poolsColor } from '../../app.constants';
 | 
			
		||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { download } from '../../shared/graphs.utils';
 | 
			
		||||
import { isMobile } from '../../shared/common.utils';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-pool-ranking',
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										34
									
								
								frontend/src/app/components/pool/pool-preview.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								frontend/src/app/components/pool/pool-preview.component.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
<div class="box preview-box" *ngIf="poolStats$ | async as poolStats">
 | 
			
		||||
  <app-preview-title>
 | 
			
		||||
    <span i18n="mining.pools">mining pool</span>
 | 
			
		||||
  </app-preview-title>
 | 
			
		||||
  <div class="row d-flex justify-content-between full-width-row">
 | 
			
		||||
    <div class="title-wrapper">
 | 
			
		||||
      <h1 class="title">{{ poolStats.pool.name }}</h1>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="logo-wrapper">
 | 
			
		||||
      <img width="62" height="62" src="/resources/mining-pools/default.svg">
 | 
			
		||||
      <img [class.noimg]="!imageLoaded" width="62" height="62" src="{{ poolStats['logo'] }}"
 | 
			
		||||
        (load)="onImageLoad()" (error)="onImageFail()">
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="row full-width-row">
 | 
			
		||||
      <div class="stats">
 | 
			
		||||
        <div class="stat-box">
 | 
			
		||||
          <div class="label" i18n="mining.tags">Tags</div>
 | 
			
		||||
          <div *ngIf="poolStats.pool.regexes.length else nodata" class="data">{{ poolStats.pool.regexes }}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="stat-box">
 | 
			
		||||
          <div class="label" i18n="mining.hashrate">Hashrate</div>
 | 
			
		||||
          <div class="data">{{ poolStats.estimatedHashrate | amountShortener : 1 : 'H/s' }}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="row hash-chart full-width-row">
 | 
			
		||||
    <div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartFinished)="onChartReady()"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<ng-template #nodata>
 | 
			
		||||
  <div>~</div>
 | 
			
		||||
</ng-template>
 | 
			
		||||
							
								
								
									
										78
									
								
								frontend/src/app/components/pool/pool-preview.component.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								frontend/src/app/components/pool/pool-preview.component.scss
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,78 @@
 | 
			
		||||
.stats {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: flex-start;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  max-width: 100%;
 | 
			
		||||
  margin: 15px 0;
 | 
			
		||||
  font-size: 32px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
 | 
			
		||||
  .stat-box {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    flex-wrap: nowrap;
 | 
			
		||||
    align-items: baseline;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    margin-left: 15px;
 | 
			
		||||
    background: #181b2d;
 | 
			
		||||
    padding: 0.75rem;
 | 
			
		||||
    width: 0;
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
 | 
			
		||||
    &:first-child {
 | 
			
		||||
      margin-left: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .label {
 | 
			
		||||
      flex-shrink: 0;
 | 
			
		||||
      flex-grow: 0;
 | 
			
		||||
      margin-right: 1em;
 | 
			
		||||
    }
 | 
			
		||||
    .data {
 | 
			
		||||
      flex-shrink: 1;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      text-overflow: ellipsis;
 | 
			
		||||
      white-space: nowrap;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chart {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 315px;
 | 
			
		||||
  background: #181b2d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.row {
 | 
			
		||||
  margin-right: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.full-width-row {
 | 
			
		||||
  padding-left: 15px;
 | 
			
		||||
  flex-wrap: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.logo-wrapper {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 62px;
 | 
			
		||||
  height: 62px;
 | 
			
		||||
  margin-left: 1em;
 | 
			
		||||
 | 
			
		||||
  img {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    background: #24273e;
 | 
			
		||||
 | 
			
		||||
    &.noimg {
 | 
			
		||||
      opacity: 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::ng-deep .symbol {
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										187
									
								
								frontend/src/app/components/pool/pool-preview.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								frontend/src/app/components/pool/pool-preview.component.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,187 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { EChartsOption, graphic } from 'echarts';
 | 
			
		||||
import { Observable, of } from 'rxjs';
 | 
			
		||||
import { map, switchMap, catchError } from 'rxjs/operators';
 | 
			
		||||
import { PoolStat } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { formatNumber } from '@angular/common';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { OpenGraphService } from '../../services/opengraph.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-pool-preview',
 | 
			
		||||
  templateUrl: './pool-preview.component.html',
 | 
			
		||||
  styleUrls: ['./pool-preview.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush
 | 
			
		||||
})
 | 
			
		||||
export class PoolPreviewComponent implements OnInit {
 | 
			
		||||
  formatNumber = formatNumber;
 | 
			
		||||
  poolStats$: Observable<PoolStat>;
 | 
			
		||||
  isLoading = true;
 | 
			
		||||
  imageLoaded = false;
 | 
			
		||||
  lastImgSrc: string = '';
 | 
			
		||||
 | 
			
		||||
  chartOptions: EChartsOption = {};
 | 
			
		||||
  chartInitOptions = {
 | 
			
		||||
    renderer: 'svg',
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  slug: string = undefined;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    @Inject(LOCALE_ID) public locale: string,
 | 
			
		||||
    private apiService: ApiService,
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
    public stateService: StateService,
 | 
			
		||||
    private seoService: SeoService,
 | 
			
		||||
    private openGraphService: OpenGraphService,
 | 
			
		||||
  ) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.poolStats$ = this.route.params.pipe(map((params) => params.slug))
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap((slug: any) => {
 | 
			
		||||
          this.isLoading = true;
 | 
			
		||||
          this.imageLoaded = false;
 | 
			
		||||
          this.slug = slug;
 | 
			
		||||
          this.openGraphService.waitFor('pool-hash-' + this.slug);
 | 
			
		||||
          this.openGraphService.waitFor('pool-stats-' + this.slug);
 | 
			
		||||
          this.openGraphService.waitFor('pool-chart-' + this.slug);
 | 
			
		||||
          this.openGraphService.waitFor('pool-img-' + this.slug);
 | 
			
		||||
          return this.apiService.getPoolHashrate$(this.slug)
 | 
			
		||||
            .pipe(
 | 
			
		||||
              switchMap((data) => {
 | 
			
		||||
                this.isLoading = false;
 | 
			
		||||
                this.prepareChartOptions(data.map(val => [val.timestamp * 1000, val.avgHashrate]));
 | 
			
		||||
                this.openGraphService.waitOver('pool-hash-' + this.slug);
 | 
			
		||||
                return [slug];
 | 
			
		||||
              }),
 | 
			
		||||
              catchError(() => {
 | 
			
		||||
                this.isLoading = false;
 | 
			
		||||
                this.openGraphService.fail('pool-hash-' + this.slug);
 | 
			
		||||
                return of([slug]);
 | 
			
		||||
              })
 | 
			
		||||
            );
 | 
			
		||||
        }),
 | 
			
		||||
        switchMap((slug) => {
 | 
			
		||||
          return this.apiService.getPoolStats$(slug).pipe(
 | 
			
		||||
            catchError(() => {
 | 
			
		||||
              this.isLoading = false;
 | 
			
		||||
              this.openGraphService.fail('pool-stats-' + this.slug);
 | 
			
		||||
              return of(null);
 | 
			
		||||
            })
 | 
			
		||||
          );
 | 
			
		||||
        }),
 | 
			
		||||
        map((poolStats) => {
 | 
			
		||||
          if (poolStats == null) {
 | 
			
		||||
            return null;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          this.seoService.setTitle(poolStats.pool.name);
 | 
			
		||||
          let regexes = '"';
 | 
			
		||||
          for (const regex of poolStats.pool.regexes) {
 | 
			
		||||
            regexes += regex + '", "';
 | 
			
		||||
          }
 | 
			
		||||
          poolStats.pool.regexes = regexes.slice(0, -3);
 | 
			
		||||
          poolStats.pool.addresses = poolStats.pool.addresses;
 | 
			
		||||
 | 
			
		||||
          if (poolStats.reportedHashrate) {
 | 
			
		||||
            poolStats.luck = poolStats.estimatedHashrate / poolStats.reportedHashrate * 100;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          this.openGraphService.waitOver('pool-stats-' + this.slug);
 | 
			
		||||
 | 
			
		||||
          const logoSrc = `/resources/mining-pools/` + poolStats.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg';
 | 
			
		||||
          if (logoSrc === this.lastImgSrc) {
 | 
			
		||||
            this.openGraphService.waitOver('pool-img-' + this.slug);
 | 
			
		||||
          }
 | 
			
		||||
          this.lastImgSrc = logoSrc;
 | 
			
		||||
          return Object.assign({
 | 
			
		||||
            logo: logoSrc
 | 
			
		||||
          }, poolStats);
 | 
			
		||||
        }),
 | 
			
		||||
        catchError(() => {
 | 
			
		||||
          this.isLoading = false;
 | 
			
		||||
          this.openGraphService.fail('pool-stats-' + this.slug);
 | 
			
		||||
          return of(null);
 | 
			
		||||
        })
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  prepareChartOptions(data) {
 | 
			
		||||
    let title: object;
 | 
			
		||||
    if (data.length === 0) {
 | 
			
		||||
      title = {
 | 
			
		||||
        textStyle: {
 | 
			
		||||
          color: 'grey',
 | 
			
		||||
          fontSize: 15
 | 
			
		||||
        },
 | 
			
		||||
        text: $localize`:@@23555386d8af1ff73f297e89dd4af3f4689fb9dd:Indexing blocks`,
 | 
			
		||||
        left: 'center',
 | 
			
		||||
        top: 'center'
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.chartOptions = {
 | 
			
		||||
      title: title,
 | 
			
		||||
      animation: false,
 | 
			
		||||
      color: [
 | 
			
		||||
        new graphic.LinearGradient(0, 0, 0, 0.65, [
 | 
			
		||||
          { offset: 0, color: '#F4511E' },
 | 
			
		||||
          { offset: 0.25, color: '#FB8C00' },
 | 
			
		||||
          { offset: 0.5, color: '#FFB300' },
 | 
			
		||||
          { offset: 0.75, color: '#FDD835' },
 | 
			
		||||
          { offset: 1, color: '#7CB342' }
 | 
			
		||||
        ]),
 | 
			
		||||
        '#D81B60',
 | 
			
		||||
      ],
 | 
			
		||||
      grid: {
 | 
			
		||||
        left: 15,
 | 
			
		||||
        right: 15,
 | 
			
		||||
        bottom: 15,
 | 
			
		||||
        top: 15,
 | 
			
		||||
        show: false,
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: data.length === 0 ? undefined : {
 | 
			
		||||
        type: 'time',
 | 
			
		||||
        show: false,
 | 
			
		||||
      },
 | 
			
		||||
      yAxis: data.length === 0 ? undefined : [
 | 
			
		||||
        {
 | 
			
		||||
          type: 'value',
 | 
			
		||||
          show: false,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      series: data.length === 0 ? undefined : [
 | 
			
		||||
        {
 | 
			
		||||
          zlevel: 0,
 | 
			
		||||
          name: 'Hashrate',
 | 
			
		||||
          showSymbol: false,
 | 
			
		||||
          symbol: 'none',
 | 
			
		||||
          data: data,
 | 
			
		||||
          type: 'line',
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            width: 4,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onChartReady(): void {
 | 
			
		||||
    this.openGraphService.waitOver('pool-chart-' + this.slug);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onImageLoad(): void {
 | 
			
		||||
    this.imageLoaded = true;
 | 
			
		||||
    this.openGraphService.waitOver('pool-img-' + this.slug);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onImageFail(): void {
 | 
			
		||||
    this.imageLoaded = false;
 | 
			
		||||
    this.openGraphService.waitOver('pool-img-' + this.slug);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -3,12 +3,12 @@ import { ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { EChartsOption, graphic } from 'echarts';
 | 
			
		||||
import { BehaviorSubject, Observable, timer } from 'rxjs';
 | 
			
		||||
import { distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { BlockExtended, PoolStat } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { selectPowerOfTen } from 'src/app/bitcoin.utils';
 | 
			
		||||
import { BlockExtended, PoolStat } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { selectPowerOfTen } from '../../bitcoin.utils';
 | 
			
		||||
import { formatNumber } from '@angular/common';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-pool',
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-push-transaction',
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Component, Input, AfterViewInit, ViewChild, ElementRef, ChangeDetectionStrategy } from '@angular/core';
 | 
			
		||||
import * as QRCode from 'qrcode';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-qrcode',
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 | 
			
		||||
import { concat, Observable } from 'rxjs';
 | 
			
		||||
import { map, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-reward-stats',
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
    <div class="search-box-container mr-2">
 | 
			
		||||
      <input (focus)="focus$.next($any($event).target.value)" (click)="click$.next($any($event).target.value)" formControlName="searchText" type="text" class="form-control" i18n-placeholder="search-form.searchbar-placeholder" placeholder="Search the full Bitcoin ecosystem">
 | 
			
		||||
      
 | 
			
		||||
      <app-search-results #searchResults [results]="typeAhead$ | async" [searchTerm]="searchForm.get('searchText').value" (selectedResult)="selectedResult($event)"></app-search-results>
 | 
			
		||||
      <app-search-results #searchResults [results]="typeAhead$ | async" (selectedResult)="selectedResult($event)"></app-search-results>
 | 
			
		||||
    
 | 
			
		||||
    </div>
 | 
			
		||||
    <div>
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,13 @@
 | 
			
		||||
import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output, ViewChild, HostListener } from '@angular/core';
 | 
			
		||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
import { AssetsService } from 'src/app/services/assets.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { AssetsService } from '../../services/assets.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { Observable, of, Subject, zip, BehaviorSubject } from 'rxjs';
 | 
			
		||||
import { debounceTime, distinctUntilChanged, switchMap, catchError, map } from 'rxjs/operators';
 | 
			
		||||
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
 | 
			
		||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { SearchResultsComponent } from './search-results/search-results.component';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
@ -74,6 +74,7 @@ export class SearchFormComponent implements OnInit {
 | 
			
		||||
        switchMap((text) => {
 | 
			
		||||
          if (!text.length) {
 | 
			
		||||
            return of([
 | 
			
		||||
              '',
 | 
			
		||||
              [],
 | 
			
		||||
              {
 | 
			
		||||
                nodes: [],
 | 
			
		||||
@ -84,11 +85,14 @@ export class SearchFormComponent implements OnInit {
 | 
			
		||||
          this.isTypeaheading$.next(true);
 | 
			
		||||
          if (!this.stateService.env.LIGHTNING) {
 | 
			
		||||
            return zip(
 | 
			
		||||
              of(text),
 | 
			
		||||
              this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([]))),
 | 
			
		||||
              [{ nodes: [], channels: [] }]
 | 
			
		||||
              [{ nodes: [], channels: [] }],
 | 
			
		||||
              of(this.regexBlockheight.test(text)),
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
          return zip(
 | 
			
		||||
            of(text),
 | 
			
		||||
            this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([]))),
 | 
			
		||||
            this.apiService.lightningSearch$(text).pipe(catchError(() => of({
 | 
			
		||||
              nodes: [],
 | 
			
		||||
@ -102,10 +106,12 @@ export class SearchFormComponent implements OnInit {
 | 
			
		||||
            return result[0].map((address: string) => 'B' + address);
 | 
			
		||||
          }
 | 
			
		||||
          return {
 | 
			
		||||
            addresses: result[0],
 | 
			
		||||
            nodes: result[1].nodes,
 | 
			
		||||
            channels: result[1].channels,
 | 
			
		||||
            totalResults: result[0].length + result[1].nodes.length + result[1].channels.length,
 | 
			
		||||
            searchText: result[0],
 | 
			
		||||
            blockHeight: this.regexBlockheight.test(result[0]) ? [parseInt(result[0], 10)] : [],
 | 
			
		||||
            addresses: result[1],
 | 
			
		||||
            nodes: result[2].nodes,
 | 
			
		||||
            channels: result[2].channels,
 | 
			
		||||
            totalResults: result[1].length + result[2].nodes.length + result[2].channels.length,
 | 
			
		||||
          };
 | 
			
		||||
        })
 | 
			
		||||
      );
 | 
			
		||||
@ -121,6 +127,8 @@ export class SearchFormComponent implements OnInit {
 | 
			
		||||
  selectedResult(result: any) {
 | 
			
		||||
    if (typeof result === 'string') {
 | 
			
		||||
      this.search(result);
 | 
			
		||||
    } else if (typeof result === 'number') {
 | 
			
		||||
      this.navigate('/block/', result.toString());
 | 
			
		||||
    } else if (result.alias) {
 | 
			
		||||
      this.navigate('/lightning/node/', result.public_key);
 | 
			
		||||
    } else if (result.short_id) {
 | 
			
		||||
 | 
			
		||||
@ -1,25 +1,31 @@
 | 
			
		||||
<div class="dropdown-menu show" *ngIf="results" [hidden]="!results.addresses.length && !results.nodes.length && !results.channels.length">
 | 
			
		||||
<div class="dropdown-menu show" *ngIf="results" [hidden]="!results.blockHeight.length && !results.addresses.length && !results.nodes.length && !results.channels.length">
 | 
			
		||||
  <ng-template [ngIf]="results.blockHeight.length">
 | 
			
		||||
    <div class="card-title">Bitcoin Block Height</div>
 | 
			
		||||
    <button (click)="clickItem(0)" [class.active]="0 === activeIdx" type="button" role="option" class="dropdown-item">
 | 
			
		||||
      Go to "{{ results.searchText }}"
 | 
			
		||||
    </button>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
  <ng-template [ngIf]="results.addresses.length">
 | 
			
		||||
    <div class="card-title" *ngIf="stateService.env.LIGHTNING">Bitcoin Addresses</div>
 | 
			
		||||
    <ng-template ngFor [ngForOf]="results.addresses" let-address let-i="index">
 | 
			
		||||
      <button (click)="clickItem(i)" [class.active]="i === activeIdx" type="button" role="option" class="dropdown-item">
 | 
			
		||||
        <ngb-highlight [result]="address | shortenString : isMobile ? 25 : 36" [term]="searchTerm"></ngb-highlight>
 | 
			
		||||
      <button (click)="clickItem(results.blockHeight.length + i)" [class.active]="(results.blockHeight.length + i) === activeIdx" type="button" role="option" class="dropdown-item">
 | 
			
		||||
        <ngb-highlight [result]="address | shortenString : isMobile ? 25 : 36" [term]="results.searchText"></ngb-highlight>
 | 
			
		||||
      </button>
 | 
			
		||||
    </ng-template>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
  <ng-template [ngIf]="results.nodes.length">
 | 
			
		||||
    <div class="card-title">Lightning Nodes</div>
 | 
			
		||||
    <ng-template ngFor [ngForOf]="results.nodes" let-node let-i="index">
 | 
			
		||||
      <button (click)="clickItem(results.addresses.length + i)" [class.inactive]="node.status === 0" [class.active]="results.addresses.length + i === activeIdx" [routerLink]="['/lightning/node' | relativeUrl, node.public_key]" type="button" role="option" class="dropdown-item">
 | 
			
		||||
        <ngb-highlight [result]="node.alias" [term]="searchTerm"></ngb-highlight>  <span class="symbol">{{ node.public_key | shortenString : 10 }}</span>
 | 
			
		||||
      <button (click)="clickItem(results.blockHeight.length + results.addresses.length + i)" [class.inactive]="node.status === 0" [class.active]="results.blockHeight.length + results.addresses.length + i === activeIdx" [routerLink]="['/lightning/node' | relativeUrl, node.public_key]" type="button" role="option" class="dropdown-item">
 | 
			
		||||
        <ngb-highlight [result]="node.alias" [term]="results.searchText"></ngb-highlight>  <span class="symbol">{{ node.public_key | shortenString : 10 }}</span>
 | 
			
		||||
      </button>
 | 
			
		||||
    </ng-template>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
  <ng-template [ngIf]="results.channels.length">
 | 
			
		||||
    <div class="card-title">Lightning Channels</div>
 | 
			
		||||
    <ng-template ngFor [ngForOf]="results.channels" let-channel let-i="index">
 | 
			
		||||
      <button (click)="clickItem(results.addresses.length + results.nodes.length + i)" [class.inactive]="channel.status === 2"  [class.active]="results.addresses.length + results.nodes.length + i === activeIdx" type="button" role="option" class="dropdown-item">
 | 
			
		||||
        <ngb-highlight [result]="channel.short_id" [term]="searchTerm"></ngb-highlight>  <span class="symbol">{{ channel.id }}</span>
 | 
			
		||||
      <button (click)="clickItem(results.blockHeight.length + results.addresses.length + results.nodes.length + i)" [class.inactive]="channel.status === 2"  [class.active]="results.blockHeight.length + results.addresses.length + results.nodes.length + i === activeIdx" type="button" role="option" class="dropdown-item">
 | 
			
		||||
        <ngb-highlight [result]="channel.short_id" [term]="results.searchText"></ngb-highlight>  <span class="symbol">{{ channel.id }}</span>
 | 
			
		||||
      </button>
 | 
			
		||||
    </ng-template>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { StateService } from '../../../services/state.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-search-results',
 | 
			
		||||
@ -8,7 +8,6 @@ import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
})
 | 
			
		||||
export class SearchResultsComponent implements OnChanges {
 | 
			
		||||
  @Input() results: any = {};
 | 
			
		||||
  @Input() searchTerm = '';
 | 
			
		||||
  @Output() selectedResult = new EventEmitter();
 | 
			
		||||
 | 
			
		||||
  isMobile = (window.innerWidth <= 767.98);
 | 
			
		||||
@ -16,12 +15,14 @@ export class SearchResultsComponent implements OnChanges {
 | 
			
		||||
  activeIdx = 0;
 | 
			
		||||
  focusFirst = true;
 | 
			
		||||
 | 
			
		||||
  constructor(public stateService: StateService) { }
 | 
			
		||||
  constructor(
 | 
			
		||||
    public stateService: StateService,
 | 
			
		||||
    ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges() {
 | 
			
		||||
    this.activeIdx = 0;
 | 
			
		||||
    if (this.results) {
 | 
			
		||||
      this.resultsFlattened = [...this.results.addresses, ...this.results.nodes, ...this.results.channels];
 | 
			
		||||
      this.resultsFlattened = [...this.results.blockHeight, ...this.results.addresses, ...this.results.nodes, ...this.results.channels];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -47,7 +48,7 @@ export class SearchResultsComponent implements OnChanges {
 | 
			
		||||
        if (this.resultsFlattened[this.activeIdx]) {
 | 
			
		||||
          this.selectedResult.emit(this.resultsFlattened[this.activeIdx]);
 | 
			
		||||
        } else {
 | 
			
		||||
          this.selectedResult.emit(this.searchTerm);
 | 
			
		||||
          this.selectedResult.emit(this.results.searchText);
 | 
			
		||||
        }
 | 
			
		||||
        this.results = null;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { specialBlocks } from 'src/app/app.constants';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { specialBlocks } from '../../app.constants';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-start',
 | 
			
		||||
 | 
			
		||||
@ -8,10 +8,10 @@ import { OptimizedMempoolStats } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { feeLevels, chartColors } from 'src/app/app.constants';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { StorageService } from '../../services/storage.service';
 | 
			
		||||
import { feeLevels, chartColors } from '../../app.constants';
 | 
			
		||||
import { MempoolGraphComponent } from '../mempool-graph/mempool-graph.component';
 | 
			
		||||
import { IncomingTransactionsGraphComponent } from '../incoming-transactions-graph/incoming-transactions-graph.component';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-status-view',
 | 
			
		||||
 | 
			
		||||
@ -35,10 +35,10 @@
 | 
			
		||||
      <path d="M 464.598 86.568 C 452.881 86.568 444.371 78.058 444.371 66.32 C 444.371 54.644 452.881 46.175 464.598 46.175 C 476.254 46.175 484.723 54.644 484.723 66.32 C 484.723 78.058 476.254 86.568 464.598 86.568 Z M 464.598 53.102 C 457.198 53.102 452.018 58.529 452.018 66.32 C 452.018 74.111 457.198 79.538 464.598 79.538 C 471.937 79.538 477.076 74.111 477.076 66.32 C 477.076 58.529 471.958 53.102 464.598 53.102 Z" fill="white"/>
 | 
			
		||||
      <path d="M 499.996 31.148 L 492.391 31.148 L 492.391 86.198 L 499.996 86.198 L 499.996 31.148 Z" fill="white"/>
 | 
			
		||||
      <path d="M124.706 110.25C124.706 118.849 117.772 125.791 109.183 125.791H15.5236C6.93387 125.791 0 118.849 0 110.25V16.4837C0 7.88416 6.98561 0.942383 15.5236 0.942383H109.183C117.772 0.942383 124.706 7.88416 124.706 16.4837V110.25Z" fill="#2E3349"/>
 | 
			
		||||
      <path d="M0 63.5225V110.25C0 118.849 6.98561 125.791 15.5753 125.791H109.183C117.772 125.791 124.758 118.849 124.758 110.25V63.5225H0Z" fill="url('#paint0_linear')"/>
 | 
			
		||||
      <path d="M0 63.5225V110.25C0 118.849 6.98561 125.791 15.5753 125.791H109.183C117.772 125.791 124.758 118.849 124.758 110.25V63.5225H0Z" [attr.fill]="'url(#paint0_linear' + randomId + ')'"/>
 | 
			
		||||
      <path opacity="0.3" d="M109.909 109.11C109.909 111.026 108.615 112.581 107.011 112.581H90.8665C89.2624 112.581 87.9688 111.026 87.9688 109.11V17.6232C87.9688 15.7065 89.2624 14.1523 90.8665 14.1523H107.011C108.615 14.1523 109.909 15.7065 109.909 17.6232V109.11Z" fill="white"/>
 | 
			
		||||
      <defs>
 | 
			
		||||
        <linearGradient id="paint0_linear" x1="62.3768" y1="36.3949" x2="62.3768" y2="156.837" gradientUnits="userSpaceOnUse">
 | 
			
		||||
        <linearGradient [id]="'paint0_linear' + randomId" x1="62.3768" y1="36.3949" x2="62.3768" y2="156.837" gradientUnits="userSpaceOnUse">
 | 
			
		||||
          <stop stop-color="#AE61FF"/>
 | 
			
		||||
          <stop offset="1" stop-color="#13EFD8"/>
 | 
			
		||||
        </linearGradient>
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
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 { StateService } from '../../services/state.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { map, scan, startWith, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { interval, merge, Observable } from 'rxjs';
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnChanges } from '@angular/core';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { dates } from 'src/app/shared/i18n/dates';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { dates } from '../../shared/i18n/dates';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-time-since',
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnChanges } from '@angular/core';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { dates } from 'src/app/shared/i18n/dates';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { dates } from '../../shared/i18n/dates';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-time-span',
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnChanges } from '@angular/core';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { dates } from 'src/app/shared/i18n/dates';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { dates } from '../../shared/i18n/dates';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-time-until',
 | 
			
		||||
 | 
			
		||||
@ -11,10 +11,10 @@ import {
 | 
			
		||||
import { Transaction, Vout } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { of, merge, Subscription, Observable, Subject, from } from 'rxjs';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { OpenGraphService } from 'src/app/services/opengraph.service';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { CpfpInfo } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { OpenGraphService } from '../../services/opengraph.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { CpfpInfo } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { LiquidUnblinding } from './liquid-ublinding';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
 | 
			
		||||
@ -190,6 +190,32 @@
 | 
			
		||||
 | 
			
		||||
    <br>
 | 
			
		||||
 | 
			
		||||
    <div class="title">
 | 
			
		||||
      <h2 i18n="transaction.flow|Transaction flow">Flow</h2>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="box">
 | 
			
		||||
      <div class="graph-container" #graphContainer>
 | 
			
		||||
        <tx-bowtie-graph
 | 
			
		||||
          [tx]="tx"
 | 
			
		||||
          [width]="graphWidth"
 | 
			
		||||
          [height]="graphExpanded ? (maxInOut * 15) : graphHeight"
 | 
			
		||||
          [lineLimit]="inOutLimit"
 | 
			
		||||
          [maxStrands]="graphExpanded ? maxInOut : 24"
 | 
			
		||||
          [network]="network"
 | 
			
		||||
          [tooltip]="true">
 | 
			
		||||
        </tx-bowtie-graph>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="toggle-wrapper" *ngIf="maxInOut > 24">
 | 
			
		||||
        <button class="btn btn-sm btn-primary graph-toggle" (click)="expandGraph();" *ngIf="!graphExpanded; else collapseBtn"><span i18n="show-more">Show more</span></button>
 | 
			
		||||
        <ng-template #collapseBtn>
 | 
			
		||||
          <button class="btn btn-sm btn-primary graph-toggle" (click)="collapseGraph();"><span i18n="show-less">Show less</span></button>
 | 
			
		||||
        </ng-template>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <br>
 | 
			
		||||
 | 
			
		||||
    <div class="title float-left">
 | 
			
		||||
      <h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
 | 
			
		||||
    </div>
 | 
			
		||||
@ -283,6 +309,36 @@
 | 
			
		||||
 | 
			
		||||
    <br>
 | 
			
		||||
 | 
			
		||||
    <div class="title">
 | 
			
		||||
      <h2 i18n="transaction.diagram|Transaction diagram">Diagram</h2>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="box">
 | 
			
		||||
      <div class="graph-container" #graphContainer style="visibility: hidden;"></div>
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <div class="col-sm">
 | 
			
		||||
          <table class="table table-borderless table-striped">
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-sm">
 | 
			
		||||
          <table class="table table-borderless table-striped">
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <br>
 | 
			
		||||
 | 
			
		||||
    <div class="title">
 | 
			
		||||
      <h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
@ -73,6 +73,24 @@
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.graph-container {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  background: #181b2d;
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  padding-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.toggle-wrapper {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  margin: 1.25em 0 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.graph-toggle {
 | 
			
		||||
  margin: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 767.98px) {
 | 
			
		||||
	.mobile-bottomcol {
 | 
			
		||||
		margin-top: 15px;
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { Component, OnInit, OnDestroy } from '@angular/core';
 | 
			
		||||
import { Component, OnInit, AfterViewInit, OnDestroy, HostListener, ViewChild, ElementRef } from '@angular/core';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
			
		||||
import {
 | 
			
		||||
@ -13,10 +13,10 @@ import { Transaction } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from } from 'rxjs';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
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 { BlockExtended, CpfpInfo } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { AudioService } from '../../services/audio.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { SeoService } from '../../services/seo.service';
 | 
			
		||||
import { BlockExtended, CpfpInfo } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { LiquidUnblinding } from './liquid-ublinding';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
@ -24,7 +24,7 @@ import { LiquidUnblinding } from './liquid-ublinding';
 | 
			
		||||
  templateUrl: './transaction.component.html',
 | 
			
		||||
  styleUrls: ['./transaction.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class TransactionComponent implements OnInit, OnDestroy {
 | 
			
		||||
export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
			
		||||
  network = '';
 | 
			
		||||
  tx: Transaction;
 | 
			
		||||
  txId: string;
 | 
			
		||||
@ -47,6 +47,16 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
			
		||||
  timeAvg$: Observable<number>;
 | 
			
		||||
  liquidUnblinding = new LiquidUnblinding();
 | 
			
		||||
  outputIndex: number;
 | 
			
		||||
  graphExpanded: boolean = false;
 | 
			
		||||
  graphWidth: number = 1000;
 | 
			
		||||
  graphHeight: number = 360;
 | 
			
		||||
  inOutLimit: number = 150;
 | 
			
		||||
  maxInOut: number = 0;
 | 
			
		||||
 | 
			
		||||
  tooltipPosition: { x: number, y: number };
 | 
			
		||||
 | 
			
		||||
  @ViewChild('graphContainer')
 | 
			
		||||
  graphContainer: ElementRef;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
@ -167,6 +177,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
			
		||||
          this.waitingForTransaction = false;
 | 
			
		||||
          this.setMempoolBlocksSubscription();
 | 
			
		||||
          this.websocketService.startTrackTransaction(tx.txid);
 | 
			
		||||
          this.setupGraph();
 | 
			
		||||
 | 
			
		||||
          if (!tx.status.confirmed && tx.firstSeen) {
 | 
			
		||||
            this.transactionTime = tx.firstSeen;
 | 
			
		||||
@ -222,6 +233,10 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngAfterViewInit(): void {
 | 
			
		||||
    this.setGraphSize();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleLoadElectrsTransactionError(error: any): Observable<any> {
 | 
			
		||||
    if (error.status === 404 && /^[a-fA-F0-9]{64}$/.test(this.txId)) {
 | 
			
		||||
      this.websocketService.startMultiTrackTransaction(this.txId);
 | 
			
		||||
@ -284,6 +299,26 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
			
		||||
    return +(cpfpTx.fee / (cpfpTx.weight / 4)).toFixed(1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setupGraph() {
 | 
			
		||||
    this.maxInOut = Math.min(this.inOutLimit, Math.max(this.tx?.vin?.length || 1, this.tx?.vout?.length + 1 || 1));
 | 
			
		||||
    this.graphHeight = Math.min(360, this.maxInOut * 80);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  expandGraph() {
 | 
			
		||||
    this.graphExpanded = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  collapseGraph() {
 | 
			
		||||
    this.graphExpanded = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @HostListener('window:resize', ['$event'])
 | 
			
		||||
  setGraphSize(): void {
 | 
			
		||||
    if (this.graphContainer) {
 | 
			
		||||
      this.graphWidth = this.graphContainer.nativeElement.clientWidth - 24;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
    this.subscription.unsubscribe();
 | 
			
		||||
    this.fetchCpfpSubscription.unsubscribe();
 | 
			
		||||
 | 
			
		||||
@ -4,10 +4,10 @@ import { Observable, ReplaySubject, BehaviorSubject, merge, Subscription } from
 | 
			
		||||
import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
import { environment } from 'src/environments/environment';
 | 
			
		||||
import { AssetsService } from 'src/app/services/assets.service';
 | 
			
		||||
import { AssetsService } from '../../services/assets.service';
 | 
			
		||||
import { filter, map, tap, switchMap } from 'rxjs/operators';
 | 
			
		||||
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { BlockExtended } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-transactions-list',
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,56 @@
 | 
			
		||||
<div
 | 
			
		||||
  #tooltip
 | 
			
		||||
  *ngIf="line"
 | 
			
		||||
  class="bowtie-graph-tooltip"
 | 
			
		||||
  [style.visibility]="line ? 'visible' : 'hidden'"
 | 
			
		||||
  [style.left]="tooltipPosition.x + 'px'"
 | 
			
		||||
  [style.top]="tooltipPosition.y + 'px'"
 | 
			
		||||
>
 | 
			
		||||
  <ng-container *ngIf="line.rest; else coinbase">
 | 
			
		||||
    <span>{{ line.rest }} </span>
 | 
			
		||||
    <ng-container [ngSwitch]="line.type">
 | 
			
		||||
      <span *ngSwitchCase="'input'" i18n="transaction.other-inputs">other inputs</span>
 | 
			
		||||
      <span *ngSwitchCase="'output'" i18n="transaction.other-outputs">other outputs</span>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
  </ng-container>
 | 
			
		||||
 | 
			
		||||
  <ng-template #coinbase>
 | 
			
		||||
    <ng-container *ngIf="line.coinbase; else pegin">
 | 
			
		||||
      <p>Coinbase</p>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
 | 
			
		||||
  <ng-template #pegin>
 | 
			
		||||
    <ng-container *ngIf="line.pegin; else pegout">
 | 
			
		||||
      <p>Peg In</p>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
 | 
			
		||||
  <ng-template #pegout>
 | 
			
		||||
    <ng-container *ngIf="line.pegout; else normal">
 | 
			
		||||
      <p>Peg Out</p>
 | 
			
		||||
      <p *ngIf="line.value != null"><app-amount [satoshis]="line.value"></app-amount></p>
 | 
			
		||||
      <p class="address">
 | 
			
		||||
        <span class="first">{{ line.pegout.slice(0, -4) }}</span>
 | 
			
		||||
        <span class="last-four">{{ line.pegout.slice(-4) }}</span>
 | 
			
		||||
      </p>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
 | 
			
		||||
  <ng-template #normal>
 | 
			
		||||
      <p>
 | 
			
		||||
        <ng-container [ngSwitch]="line.type">
 | 
			
		||||
          <span *ngSwitchCase="'input'" i18n="transaction.input">Input</span>
 | 
			
		||||
          <span *ngSwitchCase="'output'" i18n="transaction.output">Output</span>
 | 
			
		||||
          <span *ngSwitchCase="'fee'" i18n="transaction.fee">Fee</span>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
        <span *ngIf="line.type !== 'fee'"> #{{ line.index }}</span>
 | 
			
		||||
      </p>
 | 
			
		||||
      <p *ngIf="line.value == null && line.confidential" i18n="shared.confidential">Confidential</p>
 | 
			
		||||
      <p *ngIf="line.value != null"><app-amount [satoshis]="line.value"></app-amount></p>
 | 
			
		||||
      <p *ngIf="line.type !== 'fee' && line.address" class="address">
 | 
			
		||||
        <span class="first">{{ line.address.slice(0, -4) }}</span>
 | 
			
		||||
        <span class="last-four">{{ line.address.slice(-4) }}</span>
 | 
			
		||||
      </p>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,38 @@
 | 
			
		||||
.bowtie-graph-tooltip {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  background: rgba(#11131f, 0.95);
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
 | 
			
		||||
  color: #b1b1b1;
 | 
			
		||||
  padding: 10px 15px;
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  pointer-events: none;
 | 
			
		||||
  max-width: 300px;
 | 
			
		||||
 | 
			
		||||
  p {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .address {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    max-width: 100%;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    align-items: baseline;
 | 
			
		||||
    justify-content: flex-start;
 | 
			
		||||
 | 
			
		||||
    .first {
 | 
			
		||||
      flex-grow: 0;
 | 
			
		||||
      flex-shrink: 1;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      text-overflow: ellipsis;
 | 
			
		||||
      margin-right: -2px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .last-four {
 | 
			
		||||
      flex-shrink: 0;
 | 
			
		||||
      flex-grow: 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,48 @@
 | 
			
		||||
import { Component, ElementRef, ViewChild, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core';
 | 
			
		||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
 | 
			
		||||
 | 
			
		||||
interface Xput {
 | 
			
		||||
  type: 'input' | 'output' | 'fee';
 | 
			
		||||
  value?: number;
 | 
			
		||||
  index?: number;
 | 
			
		||||
  address?: string;
 | 
			
		||||
  rest?: number;
 | 
			
		||||
  coinbase?: boolean;
 | 
			
		||||
  pegin?: boolean;
 | 
			
		||||
  pegout?: string;
 | 
			
		||||
  confidential?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-tx-bowtie-graph-tooltip',
 | 
			
		||||
  templateUrl: './tx-bowtie-graph-tooltip.component.html',
 | 
			
		||||
  styleUrls: ['./tx-bowtie-graph-tooltip.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class TxBowtieGraphTooltipComponent implements OnChanges {
 | 
			
		||||
  @Input() line: Xput | void;
 | 
			
		||||
  @Input() cursorPosition: { x: number, y: number };
 | 
			
		||||
 | 
			
		||||
  tooltipPosition = { x: 0, y: 0 };
 | 
			
		||||
 | 
			
		||||
  @ViewChild('tooltip') tooltipElement: ElementRef<HTMLCanvasElement>;
 | 
			
		||||
 | 
			
		||||
  constructor() {}
 | 
			
		||||
 | 
			
		||||
  ngOnChanges(changes): void {
 | 
			
		||||
    if (changes.cursorPosition && changes.cursorPosition.currentValue) {
 | 
			
		||||
      let x = Math.max(10, changes.cursorPosition.currentValue.x - 50);
 | 
			
		||||
      let y = changes.cursorPosition.currentValue.y + 20;
 | 
			
		||||
      if (this.tooltipElement) {
 | 
			
		||||
        const elementBounds = this.tooltipElement.nativeElement.getBoundingClientRect();
 | 
			
		||||
        const parentBounds = this.tooltipElement.nativeElement.offsetParent.getBoundingClientRect();
 | 
			
		||||
        if ((parentBounds.left + x + elementBounds.width) > parentBounds.right) {
 | 
			
		||||
          x = Math.max(0, parentBounds.width - elementBounds.width - 10);
 | 
			
		||||
        }
 | 
			
		||||
        if (y + elementBounds.height > parentBounds.height) {
 | 
			
		||||
          y = y - elementBounds.height - 20;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      this.tooltipPosition = { x, y };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,44 +1,82 @@
 | 
			
		||||
<svg *ngIf="inputs && outputs" class="bowtie" [attr.height]="(height + 10) + 'px'" [attr.width]="width + 'px'">
 | 
			
		||||
  <defs>
 | 
			
		||||
    <marker id="input-arrow" viewBox="-5 -5 10 10"
 | 
			
		||||
        refX="0" refY="0"
 | 
			
		||||
        markerUnits="strokeWidth"
 | 
			
		||||
        markerWidth="1.5" markerHeight="1"
 | 
			
		||||
        orient="auto">
 | 
			
		||||
      <path d="M -5 -5 L 0 0 L -5 5 L 1 5 L 1 -5 Z" stroke-width="0" [attr.fill]="gradient[0]"/>
 | 
			
		||||
    </marker>
 | 
			
		||||
    <marker id="output-arrow" viewBox="-5 -5 10 10"
 | 
			
		||||
        refX="0" refY="0"
 | 
			
		||||
        markerUnits="strokeWidth"
 | 
			
		||||
        markerWidth="1.5" markerHeight="1"
 | 
			
		||||
        orient="auto">
 | 
			
		||||
      <path d="M 1 -5 L 0 -5 L -5 0 L 0 5 L 1 5 Z" stroke-width="0" [attr.fill]="gradient[0]"/>
 | 
			
		||||
    </marker>
 | 
			
		||||
    <marker id="fee-arrow" viewBox="-5 -5 10 10"
 | 
			
		||||
        refX="0" refY="0"
 | 
			
		||||
        markerUnits="strokeWidth"
 | 
			
		||||
        markerWidth="1.5" markerHeight="1"
 | 
			
		||||
        orient="auto">
 | 
			
		||||
    </marker>
 | 
			
		||||
    <linearGradient id="input-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
			
		||||
<div class="bowtie-graph">
 | 
			
		||||
  <svg *ngIf="inputs && outputs" class="bowtie" [attr.height]="(height + 10) + 'px'" [attr.width]="width + 'px'">
 | 
			
		||||
    <defs>
 | 
			
		||||
      <marker id="input-arrow" viewBox="-5 -5 10 10"
 | 
			
		||||
          refX="0" refY="0"
 | 
			
		||||
          markerUnits="strokeWidth"
 | 
			
		||||
          markerWidth="1.5" markerHeight="1"
 | 
			
		||||
          orient="auto">
 | 
			
		||||
        <path d="M -5 -5 L 0 0 L -5 5 L 1 5 L 1 -5 Z" stroke-width="0" [attr.fill]="gradient[0]"/>
 | 
			
		||||
      </marker>
 | 
			
		||||
      <marker id="output-arrow" viewBox="-5 -5 10 10"
 | 
			
		||||
          refX="0" refY="0"
 | 
			
		||||
          markerUnits="strokeWidth"
 | 
			
		||||
          markerWidth="1.5" markerHeight="1"
 | 
			
		||||
          orient="auto">
 | 
			
		||||
        <path d="M 1 -5 L 0 -5 L -5 0 L 0 5 L 1 5 Z" stroke-width="0" [attr.fill]="gradient[0]"/>
 | 
			
		||||
      </marker>
 | 
			
		||||
      <marker id="fee-arrow" viewBox="-5 -5 10 10"
 | 
			
		||||
          refX="0" refY="0"
 | 
			
		||||
          markerUnits="strokeWidth"
 | 
			
		||||
          markerWidth="1.5" markerHeight="1"
 | 
			
		||||
          orient="auto">
 | 
			
		||||
      </marker>
 | 
			
		||||
      <linearGradient id="input-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
			
		||||
        <stop offset="0%" [attr.stop-color]="gradient[0]" />
 | 
			
		||||
        <stop offset="100%" [attr.stop-color]="gradient[1]" />
 | 
			
		||||
      </linearGradient>
 | 
			
		||||
      <linearGradient id="output-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
			
		||||
        <stop offset="0%" [attr.stop-color]="gradient[1]" />
 | 
			
		||||
        <stop offset="100%" [attr.stop-color]="gradient[0]" />
 | 
			
		||||
      </linearGradient>
 | 
			
		||||
      <linearGradient id="input-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
			
		||||
      <stop offset="0%" [attr.stop-color]="gradient[0]" />
 | 
			
		||||
      <stop offset="100%" [attr.stop-color]="gradient[1]" />
 | 
			
		||||
    </linearGradient>
 | 
			
		||||
    <linearGradient id="output-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
			
		||||
      <stop offset="0%" [attr.stop-color]="gradient[1]" />
 | 
			
		||||
      <stop offset="100%" [attr.stop-color]="gradient[0]" />
 | 
			
		||||
    </linearGradient>
 | 
			
		||||
    <linearGradient id="fee-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
			
		||||
      <stop offset="0%" [attr.stop-color]="gradient[1]" />
 | 
			
		||||
      <stop offset="50%" [attr.stop-color]="gradient[1]" />
 | 
			
		||||
      <stop offset="100%" stop-color="transparent" />
 | 
			
		||||
    </linearGradient>
 | 
			
		||||
  </defs>
 | 
			
		||||
  <path [attr.d]="middle.path" class="line middle" [style]="middle.style"/>
 | 
			
		||||
  <ng-container *ngFor="let input of inputs">
 | 
			
		||||
    <path [attr.d]="input.path" class="line {{input.class}}" [style]="input.style" attr.marker-start="url(#{{input.class}}-arrow)"/>
 | 
			
		||||
  </ng-container>
 | 
			
		||||
  <ng-container *ngFor="let output of outputs">
 | 
			
		||||
    <path [attr.d]="output.path" class="line {{output.class}}" [style]="output.style" attr.marker-start="url(#{{output.class}}-arrow)" />
 | 
			
		||||
  </ng-container>
 | 
			
		||||
</svg>
 | 
			
		||||
      <stop offset="2%" [attr.stop-color]="gradient[0]" />
 | 
			
		||||
        <stop offset="30%" stop-color="white" />
 | 
			
		||||
        <stop offset="100%" [attr.stop-color]="gradient[1]" />
 | 
			
		||||
      </linearGradient>
 | 
			
		||||
      <linearGradient id="output-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
			
		||||
        <stop offset="0%" [attr.stop-color]="gradient[1]" />
 | 
			
		||||
        <stop offset="70%" stop-color="white" />
 | 
			
		||||
        <stop offset="98%" [attr.stop-color]="gradient[0]" />
 | 
			
		||||
        <stop offset="100%" [attr.stop-color]="gradient[0]" />
 | 
			
		||||
      </linearGradient>
 | 
			
		||||
      <linearGradient id="fee-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
			
		||||
        <stop offset="0%" [attr.stop-color]="gradient[1]" />
 | 
			
		||||
        <stop offset="100%" stop-color="white" />
 | 
			
		||||
      </linearGradient>
 | 
			
		||||
      <linearGradient id="fee-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
			
		||||
        <stop offset="0%" [attr.stop-color]="gradient[1]" />
 | 
			
		||||
        <stop offset="50%" [attr.stop-color]="gradient[1]" />
 | 
			
		||||
        <stop offset="100%" stop-color="transparent" />
 | 
			
		||||
      </linearGradient>
 | 
			
		||||
    </defs>
 | 
			
		||||
    <path [attr.d]="middle.path" class="line middle" [style]="middle.style"/>
 | 
			
		||||
    <ng-container *ngFor="let input of inputs; let i = index">
 | 
			
		||||
      <path
 | 
			
		||||
        [attr.d]="input.path"
 | 
			
		||||
        class="line {{input.class}}"
 | 
			
		||||
        [style]="input.style"
 | 
			
		||||
        attr.marker-start="url(#{{input.class}}-arrow)"
 | 
			
		||||
        (pointerover)="onHover($event, 'input', i);"
 | 
			
		||||
        (pointerout)="onBlur($event, 'input', i);"
 | 
			
		||||
      />
 | 
			
		||||
    </ng-container>
 | 
			
		||||
    <ng-container *ngFor="let output of outputs; let i = index">
 | 
			
		||||
      <path
 | 
			
		||||
        [attr.d]="output.path"
 | 
			
		||||
        class="line {{output.class}}"
 | 
			
		||||
        [style]="output.style"
 | 
			
		||||
        attr.marker-start="url(#{{output.class}}-arrow)"
 | 
			
		||||
        (pointerover)="onHover($event, 'output', i);"
 | 
			
		||||
        (pointerout)="onBlur($event, 'output', i);"
 | 
			
		||||
      />
 | 
			
		||||
    </ng-container>
 | 
			
		||||
  </svg>
 | 
			
		||||
 | 
			
		||||
  <app-tx-bowtie-graph-tooltip
 | 
			
		||||
    *ngIf=[tooltip]
 | 
			
		||||
    [line]="hoverLine"
 | 
			
		||||
    [cursorPosition]="tooltipPosition"
 | 
			
		||||
  ></app-tx-bowtie-graph-tooltip>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 3.5 KiB  | 
@ -11,5 +11,19 @@
 | 
			
		||||
    &.fee {
 | 
			
		||||
      stroke: url(#fee-gradient);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      z-index: 10;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
      &.input {
 | 
			
		||||
        stroke: url(#input-hover-gradient);
 | 
			
		||||
      }
 | 
			
		||||
      &.output {
 | 
			
		||||
        stroke: url(#output-hover-gradient);
 | 
			
		||||
      }
 | 
			
		||||
      &.fee {
 | 
			
		||||
        stroke: url(#fee-hover-gradient);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { Component, OnInit, Input, OnChanges } from '@angular/core';
 | 
			
		||||
import { Component, OnInit, Input, OnChanges, HostListener } from '@angular/core';
 | 
			
		||||
import { Transaction } from '../../interfaces/electrs.interface';
 | 
			
		||||
 | 
			
		||||
interface SvgLine {
 | 
			
		||||
@ -7,6 +7,18 @@ interface SvgLine {
 | 
			
		||||
  class?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface Xput {
 | 
			
		||||
  type: 'input' | 'output' | 'fee';
 | 
			
		||||
  value?: number;
 | 
			
		||||
  index?: number;
 | 
			
		||||
  address?: string;
 | 
			
		||||
  rest?: number;
 | 
			
		||||
  coinbase?: boolean;
 | 
			
		||||
  pegin?: boolean;
 | 
			
		||||
  pegout?: string;
 | 
			
		||||
  confidential?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tx-bowtie-graph',
 | 
			
		||||
  templateUrl: './tx-bowtie-graph.component.html',
 | 
			
		||||
@ -17,14 +29,22 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
  @Input() network: string;
 | 
			
		||||
  @Input() width = 1200;
 | 
			
		||||
  @Input() height = 600;
 | 
			
		||||
  @Input() combinedWeight = 100;
 | 
			
		||||
  @Input() lineLimit = 250;
 | 
			
		||||
  @Input() maxCombinedWeight = 100;
 | 
			
		||||
  @Input() minWeight = 2; //
 | 
			
		||||
  @Input() maxStrands = 24; // number of inputs/outputs to keep fully on-screen.
 | 
			
		||||
  @Input() tooltip = false;
 | 
			
		||||
 | 
			
		||||
  inputData: Xput[];
 | 
			
		||||
  outputData: Xput[];
 | 
			
		||||
  inputs: SvgLine[];
 | 
			
		||||
  outputs: SvgLine[];
 | 
			
		||||
  middle: SvgLine;
 | 
			
		||||
  midWidth: number;
 | 
			
		||||
  combinedWeight: number;
 | 
			
		||||
  isLiquid: boolean = false;
 | 
			
		||||
  hoverLine: Xput | void = null;
 | 
			
		||||
  tooltipPosition = { x: 0, y: 0 };
 | 
			
		||||
 | 
			
		||||
  gradientColors = {
 | 
			
		||||
    '': ['#9339f4', '#105fb0'],
 | 
			
		||||
@ -42,31 +62,70 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
  gradient: string[] = ['#105fb0', '#105fb0'];
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet');
 | 
			
		||||
    this.gradient = this.gradientColors[this.network];
 | 
			
		||||
    this.initGraph();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges(): void {
 | 
			
		||||
    this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet');
 | 
			
		||||
    this.gradient = this.gradientColors[this.network];
 | 
			
		||||
    this.initGraph();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  initGraph(): void {
 | 
			
		||||
    this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet');
 | 
			
		||||
    this.gradient = this.gradientColors[this.network];
 | 
			
		||||
    this.midWidth = Math.min(10, Math.ceil(this.width / 100));
 | 
			
		||||
    this.combinedWeight = Math.min(this.maxCombinedWeight, Math.floor((this.width - (2 * this.midWidth)) / 6));
 | 
			
		||||
 | 
			
		||||
    const totalValue = this.calcTotalValue(this.tx);
 | 
			
		||||
    const voutWithFee = this.tx.vout.map(v => { return { type: v.scriptpubkey_type === 'fee' ? 'fee' : 'output', value: v?.value }; });
 | 
			
		||||
    let voutWithFee = this.tx.vout.map(v => {
 | 
			
		||||
      return {
 | 
			
		||||
        type: v.scriptpubkey_type === 'fee' ? 'fee' : 'output',
 | 
			
		||||
        value: v?.value,
 | 
			
		||||
        address: v?.scriptpubkey_address || v?.scriptpubkey_type?.toUpperCase(),
 | 
			
		||||
        pegout: v?.pegout?.scriptpubkey_address,
 | 
			
		||||
        confidential: (this.isLiquid && v?.value === undefined),
 | 
			
		||||
      } as Xput;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (this.tx.fee && !this.isLiquid) {
 | 
			
		||||
      voutWithFee.unshift({ type: 'fee', value: this.tx.fee });
 | 
			
		||||
    }
 | 
			
		||||
    const outputCount = voutWithFee.length;
 | 
			
		||||
 | 
			
		||||
    this.inputs = this.initLines('in', this.tx.vin.map(v => { return {type: 'input', value: v?.prevout?.value }; }), totalValue, this.maxStrands);
 | 
			
		||||
    let truncatedInputs = this.tx.vin.map(v => {
 | 
			
		||||
      return {
 | 
			
		||||
        type: 'input',
 | 
			
		||||
        value: v?.prevout?.value,
 | 
			
		||||
        address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(),
 | 
			
		||||
        coinbase: v?.is_coinbase,
 | 
			
		||||
        pegin: v?.is_pegin,
 | 
			
		||||
        confidential: (this.isLiquid && v?.prevout?.value === undefined),
 | 
			
		||||
      } as Xput;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (truncatedInputs.length > this.lineLimit) {
 | 
			
		||||
      const valueOfRest = truncatedInputs.slice(this.lineLimit).reduce((r, v) => {
 | 
			
		||||
        return r + (v.value || 0);
 | 
			
		||||
      }, 0);
 | 
			
		||||
      truncatedInputs = truncatedInputs.slice(0, this.lineLimit);
 | 
			
		||||
      truncatedInputs.push({ type: 'input', value: valueOfRest, rest: this.tx.vin.length - this.lineLimit });
 | 
			
		||||
    }
 | 
			
		||||
    if (voutWithFee.length > this.lineLimit) {
 | 
			
		||||
      const valueOfRest = voutWithFee.slice(this.lineLimit).reduce((r, v) => {
 | 
			
		||||
        return r + (v.value || 0);
 | 
			
		||||
      }, 0);
 | 
			
		||||
      voutWithFee = voutWithFee.slice(0, this.lineLimit);
 | 
			
		||||
      voutWithFee.push({ type: 'output', value: valueOfRest, rest: outputCount - this.lineLimit });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.inputData = truncatedInputs;
 | 
			
		||||
    this.outputData = voutWithFee;
 | 
			
		||||
 | 
			
		||||
    this.inputs = this.initLines('in', truncatedInputs, totalValue, this.maxStrands);
 | 
			
		||||
    this.outputs = this.initLines('out', voutWithFee, totalValue, this.maxStrands);
 | 
			
		||||
 | 
			
		||||
    this.middle = {
 | 
			
		||||
      path: `M ${(this.width / 2) - 50} ${(this.height / 2) + 0.5} L ${(this.width / 2) + 50} ${(this.height / 2) + 0.5}`,
 | 
			
		||||
      style: `stroke-width: ${this.combinedWeight + 0.5}; stroke: ${this.gradient[1]}`
 | 
			
		||||
      path: `M ${(this.width / 2) - this.midWidth} ${(this.height / 2) + 0.5} L ${(this.width / 2) + this.midWidth} ${(this.height / 2) + 0.5}`,
 | 
			
		||||
      style: `stroke-width: ${this.combinedWeight + 1}; stroke: ${this.gradient[1]}`
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -95,9 +154,9 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  initLines(side: 'in' | 'out', xputs: { type: string, value: number | void }[], total: number, maxVisibleStrands: number): SvgLine[] {
 | 
			
		||||
  initLines(side: 'in' | 'out', xputs: Xput[], total: number, maxVisibleStrands: number): SvgLine[] {
 | 
			
		||||
    if (!total) {
 | 
			
		||||
      const weights = xputs.map((put): number => this.combinedWeight / xputs.length);
 | 
			
		||||
      const weights = xputs.map((put) => this.combinedWeight / xputs.length);
 | 
			
		||||
      return this.linesFromWeights(side, xputs, weights, maxVisibleStrands);
 | 
			
		||||
    } else {
 | 
			
		||||
      let unknownCount = 0;
 | 
			
		||||
@ -111,19 +170,26 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
      });
 | 
			
		||||
      const unknownShare = unknownTotal / unknownCount;
 | 
			
		||||
      // conceptual weights
 | 
			
		||||
      const weights = xputs.map((put): number => this.combinedWeight * (put.value == null ? unknownShare : put.value as number) / total);
 | 
			
		||||
      const weights = xputs.map((put) => this.combinedWeight * (put.value == null ? unknownShare : put.value as number) / total);
 | 
			
		||||
      return this.linesFromWeights(side, xputs, weights, maxVisibleStrands);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  linesFromWeights(side: 'in' | 'out', xputs: { type: string, value: number | void }[], weights: number[], maxVisibleStrands: number) {
 | 
			
		||||
    const lines = [];
 | 
			
		||||
    // actual displayed line thicknesses
 | 
			
		||||
    const minWeights = weights.map((w) => Math.max(this.minWeight - 1, w) + 1);
 | 
			
		||||
  linesFromWeights(side: 'in' | 'out', xputs: Xput[], weights: number[], maxVisibleStrands: number): SvgLine[] {
 | 
			
		||||
    const lineParams = weights.map((w) => {
 | 
			
		||||
      return {
 | 
			
		||||
        weight: w,
 | 
			
		||||
        thickness: Math.max(this.minWeight - 1, w) + 1,
 | 
			
		||||
        offset: 0,
 | 
			
		||||
        innerY: 0,
 | 
			
		||||
        outerY: 0,
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
    const visibleStrands = Math.min(maxVisibleStrands, xputs.length);
 | 
			
		||||
    const visibleWeight = minWeights.slice(0, visibleStrands).reduce((acc, v) => v + acc, 0);
 | 
			
		||||
    const visibleWeight = lineParams.slice(0, visibleStrands).reduce((acc, v) => v.thickness + acc, 0);
 | 
			
		||||
    const gaps = visibleStrands - 1;
 | 
			
		||||
 | 
			
		||||
    // bounds of the middle segment
 | 
			
		||||
    const innerTop = (this.height / 2) - (this.combinedWeight / 2);
 | 
			
		||||
    const innerBottom = innerTop + this.combinedWeight;
 | 
			
		||||
    // tracks the visual bottom of the endpoints of the previous line
 | 
			
		||||
@ -132,46 +198,121 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
    // gap between strands
 | 
			
		||||
    const spacing = (this.height - visibleWeight) / gaps;
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < xputs.length; i++) {
 | 
			
		||||
      const weight = weights[i];
 | 
			
		||||
      const minWeight = minWeights[i];
 | 
			
		||||
    // curve adjustments to prevent overlaps
 | 
			
		||||
    let offset = 0;
 | 
			
		||||
    let minOffset = 0;
 | 
			
		||||
    let maxOffset = 0;
 | 
			
		||||
    let lastWeight = 0;
 | 
			
		||||
    let pad = 0;
 | 
			
		||||
    lineParams.forEach((line, i) => {
 | 
			
		||||
      // set the vertical position of the (center of the) outer side of the line
 | 
			
		||||
      let outer = lastOuter + (minWeight / 2);
 | 
			
		||||
      const inner = Math.min(innerBottom + (minWeight / 2), Math.max(innerTop + (minWeight / 2), lastInner + (weight / 2)));
 | 
			
		||||
      line.outerY = lastOuter + (line.thickness / 2);
 | 
			
		||||
      line.innerY = Math.min(innerBottom + (line.thickness / 2), Math.max(innerTop + (line.thickness / 2), lastInner + (line.weight / 2)));
 | 
			
		||||
 | 
			
		||||
      // special case to center single input/outputs
 | 
			
		||||
      if (xputs.length === 1) {
 | 
			
		||||
        outer = (this.height / 2);
 | 
			
		||||
        line.outerY = (this.height / 2);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      lastOuter += minWeight + spacing;
 | 
			
		||||
      lastInner += weight;
 | 
			
		||||
      lines.push({
 | 
			
		||||
        path: this.makePath(side, outer, inner, minWeight),
 | 
			
		||||
        style: this.makeStyle(minWeight, xputs[i].type),
 | 
			
		||||
        class: xputs[i].type
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
      lastOuter += line.thickness + spacing;
 | 
			
		||||
      lastInner += line.weight;
 | 
			
		||||
 | 
			
		||||
    return lines;
 | 
			
		||||
      // calculate conservative lower bound of the amount of horizontal offset
 | 
			
		||||
      // required to prevent this line overlapping its neighbor
 | 
			
		||||
 | 
			
		||||
      if (this.tooltip || !xputs[i].rest) {
 | 
			
		||||
        const w = (this.width - Math.max(lastWeight, line.weight)) / 2; // approximate horizontal width of the curved section of the line
 | 
			
		||||
        const y1 = line.outerY;
 | 
			
		||||
        const y2 = line.innerY;
 | 
			
		||||
        const t = (lastWeight + line.weight) / 2; // distance between center of this line and center of previous line
 | 
			
		||||
 | 
			
		||||
        // slope of the inflection point of the bezier curve
 | 
			
		||||
        const dx = 0.75 * w;
 | 
			
		||||
        const dy = 1.5 * (y2 - y1);
 | 
			
		||||
        const a = Math.atan2(dy, dx);
 | 
			
		||||
 | 
			
		||||
        // parallel curves should be separated by >=t at the inflection point to prevent overlap
 | 
			
		||||
        // vertical offset is always = t, contributing tCos(a)
 | 
			
		||||
        // horizontal offset h will contribute hSin(a)
 | 
			
		||||
        // tCos(a) + hSin(a) >= t
 | 
			
		||||
        // h >= t(1 - cos(a)) / sin(a)
 | 
			
		||||
        if (Math.sin(a) !== 0) {
 | 
			
		||||
          // (absolute value clamped to t for sanity)
 | 
			
		||||
          offset += Math.max(Math.min(t * (1 - Math.cos(a)) / Math.sin(a), t), -t);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        line.offset = offset;
 | 
			
		||||
        minOffset = Math.min(minOffset, offset);
 | 
			
		||||
        maxOffset = Math.max(maxOffset, offset);
 | 
			
		||||
        pad = Math.max(pad, line.thickness / 2);
 | 
			
		||||
        lastWeight = line.weight;
 | 
			
		||||
      } else {
 | 
			
		||||
        // skip the offsets for consolidated lines in unfurls, since these *should* overlap a little
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // normalize offsets
 | 
			
		||||
    lineParams.forEach((line) => {
 | 
			
		||||
      line.offset -= minOffset;
 | 
			
		||||
    });
 | 
			
		||||
    maxOffset -= minOffset;
 | 
			
		||||
 | 
			
		||||
    return lineParams.map((line, i) => {
 | 
			
		||||
      return {
 | 
			
		||||
        path: this.makePath(side, line.outerY, line.innerY, line.thickness, line.offset, pad + maxOffset),
 | 
			
		||||
        style: this.makeStyle(line.thickness, xputs[i].type),
 | 
			
		||||
        class: xputs[i].type
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  makePath(side: 'in' | 'out', outer: number, inner: number, weight: number): string {
 | 
			
		||||
    const start = side === 'in' ? (weight * 0.5) : this.width - (weight * 0.5);
 | 
			
		||||
    const center =  this.width / 2 + (side === 'in' ? -45 : 45 );
 | 
			
		||||
    const midpoint = (start + center) / 2;
 | 
			
		||||
  makePath(side: 'in' | 'out', outer: number, inner: number, weight: number, offset: number, pad: number): string {
 | 
			
		||||
    const start = (weight * 0.5);
 | 
			
		||||
    const curveStart = Math.max(start + 1, pad - offset);
 | 
			
		||||
    const end =  this.width / 2 - (this.midWidth * 0.9) + 1;
 | 
			
		||||
    const curveEnd = end - offset - 10;
 | 
			
		||||
    const midpoint = (curveStart + curveEnd) / 2;
 | 
			
		||||
 | 
			
		||||
    // correct for svg horizontal gradient bug
 | 
			
		||||
    if (Math.round(outer) === Math.round(inner)) {
 | 
			
		||||
      outer -= 1;
 | 
			
		||||
    }
 | 
			
		||||
    return `M ${start} ${outer} C ${midpoint} ${outer}, ${midpoint} ${inner}, ${center} ${inner}`;
 | 
			
		||||
 | 
			
		||||
    if (side === 'in') {
 | 
			
		||||
      return `M ${start} ${outer} L ${curveStart} ${outer} C ${midpoint} ${outer}, ${midpoint} ${inner}, ${curveEnd} ${inner} L ${end} ${inner}`;
 | 
			
		||||
    } else { // mirrored in y-axis for the right hand side
 | 
			
		||||
      return `M ${this.width - start} ${outer} L ${this.width - curveStart} ${outer} C ${this.width - midpoint} ${outer}, ${this.width - midpoint} ${inner}, ${this.width - curveEnd} ${inner} L ${this.width - end} ${inner}`;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  makeStyle(minWeight, type): string {
 | 
			
		||||
    if (type === 'fee') {
 | 
			
		||||
      return `stroke-width: ${minWeight}; stroke: url(#fee-gradient)`;
 | 
			
		||||
      return `stroke-width: ${minWeight}`;
 | 
			
		||||
    } else {
 | 
			
		||||
      return `stroke-width: ${minWeight}`;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @HostListener('pointermove', ['$event'])
 | 
			
		||||
  onPointerMove(event) {
 | 
			
		||||
    this.tooltipPosition = { x: event.offsetX, y: event.offsetY };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onHover(event, side, index): void {
 | 
			
		||||
    if (side === 'input') {
 | 
			
		||||
      this.hoverLine = {
 | 
			
		||||
        ...this.inputData[index],
 | 
			
		||||
        index
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      this.hoverLine = {
 | 
			
		||||
        ...this.outputData[index],
 | 
			
		||||
        index
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onBlur(event, side, index): void {
 | 
			
		||||
    this.hoverLine = null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Component, ChangeDetectionStrategy, OnChanges, Input } from '@angular/core';
 | 
			
		||||
import { calcSegwitFeeGains } from 'src/app/bitcoin.utils';
 | 
			
		||||
import { Transaction } from 'src/app/interfaces/electrs.interface';
 | 
			
		||||
import { calcSegwitFeeGains } from '../../bitcoin.utils';
 | 
			
		||||
import { Transaction } from '../../interfaces/electrs.interface';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-tx-features',
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { Component, ChangeDetectionStrategy, OnChanges, Input, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core';
 | 
			
		||||
import { Transaction } from 'src/app/interfaces/electrs.interface';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { Transaction } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { Subscription } from 'rxjs';
 | 
			
		||||
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { BlockExtended } from '../../interfaces/node-api.interface';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-tx-fee-rating',
 | 
			
		||||
 | 
			
		||||
@ -17,50 +17,50 @@ export const wsApiDocsData = {
 | 
			
		||||
  codeTemplate: {
 | 
			
		||||
    curl: `/api/v1/ws`,
 | 
			
		||||
    commonJS: `
 | 
			
		||||
  const { %{0}: { websocket } } = mempoolJS();
 | 
			
		||||
        const { %{0}: { websocket } } = mempoolJS();
 | 
			
		||||
 | 
			
		||||
  const ws = websocket.initClient({
 | 
			
		||||
    options: ['blocks', 'stats', 'mempool-blocks', 'live-2h-chart'],
 | 
			
		||||
  });
 | 
			
		||||
        const ws = websocket.initClient({
 | 
			
		||||
          options: ['blocks', 'stats', 'mempool-blocks', 'live-2h-chart'],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
  ws.addEventListener('message', function incoming({data}) {
 | 
			
		||||
    const res = JSON.parse(data.toString());
 | 
			
		||||
    if (res.block) {
 | 
			
		||||
      document.getElementById("result-blocks").textContent = JSON.stringify(res.block, undefined, 2);
 | 
			
		||||
    }
 | 
			
		||||
    if (res.mempoolInfo) {
 | 
			
		||||
      document.getElementById("result-mempool-info").textContent = JSON.stringify(res.mempoolInfo, undefined, 2);
 | 
			
		||||
    }
 | 
			
		||||
    if (res.transactions) {
 | 
			
		||||
      document.getElementById("result-transactions").textContent = JSON.stringify(res.transactions, undefined, 2);
 | 
			
		||||
    }
 | 
			
		||||
    if (res["mempool-blocks"]) {
 | 
			
		||||
      document.getElementById("result-mempool-blocks").textContent = JSON.stringify(res["mempool-blocks"], undefined, 2);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
        ws.addEventListener('message', function incoming({data}) {
 | 
			
		||||
          const res = JSON.parse(data.toString());
 | 
			
		||||
          if (res.block) {
 | 
			
		||||
            document.getElementById("result-blocks").textContent = JSON.stringify(res.block, undefined, 2);
 | 
			
		||||
          }
 | 
			
		||||
          if (res.mempoolInfo) {
 | 
			
		||||
            document.getElementById("result-mempool-info").textContent = JSON.stringify(res.mempoolInfo, undefined, 2);
 | 
			
		||||
          }
 | 
			
		||||
          if (res.transactions) {
 | 
			
		||||
            document.getElementById("result-transactions").textContent = JSON.stringify(res.transactions, undefined, 2);
 | 
			
		||||
          }
 | 
			
		||||
          if (res["mempool-blocks"]) {
 | 
			
		||||
            document.getElementById("result-mempool-blocks").textContent = JSON.stringify(res["mempool-blocks"], undefined, 2);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
  `,
 | 
			
		||||
    esModule: `
 | 
			
		||||
const { %{0}: { websocket } } = mempoolJS();
 | 
			
		||||
  const { %{0}: { websocket } } = mempoolJS();
 | 
			
		||||
 | 
			
		||||
const ws = websocket.initServer({
 | 
			
		||||
options: ["blocks", "stats", "mempool-blocks", "live-2h-chart"],
 | 
			
		||||
});
 | 
			
		||||
  const ws = websocket.initServer({
 | 
			
		||||
    options: ["blocks", "stats", "mempool-blocks", "live-2h-chart"],
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
ws.on("message", function incoming(data) {
 | 
			
		||||
const res = JSON.parse(data.toString());
 | 
			
		||||
if (res.block) {
 | 
			
		||||
console.log(res.block);
 | 
			
		||||
}
 | 
			
		||||
if (res.mempoolInfo) {
 | 
			
		||||
console.log(res.mempoolInfo);
 | 
			
		||||
}
 | 
			
		||||
if (res.transactions) {
 | 
			
		||||
console.log(res.transactions);
 | 
			
		||||
}
 | 
			
		||||
if (res["mempool-blocks"]) {
 | 
			
		||||
console.log(res["mempool-blocks"]);
 | 
			
		||||
}
 | 
			
		||||
});
 | 
			
		||||
  ws.on("message", function incoming(data) {
 | 
			
		||||
    const res = JSON.parse(data.toString());
 | 
			
		||||
    if (res.block) {
 | 
			
		||||
      console.log(res.block);
 | 
			
		||||
    }
 | 
			
		||||
    if (res.mempoolInfo) {
 | 
			
		||||
      console.log(res.mempoolInfo);
 | 
			
		||||
    }
 | 
			
		||||
    if (res.transactions) {
 | 
			
		||||
      console.log(res.transactions);
 | 
			
		||||
    }
 | 
			
		||||
    if (res["mempool-blocks"]) {
 | 
			
		||||
      console.log(res["mempool-blocks"]);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
    `,
 | 
			
		||||
    python: `import websocket
 | 
			
		||||
import _thread
 | 
			
		||||
 | 
			
		||||
@ -152,6 +152,7 @@ export class CodeTemplateComponent implements OnInit {
 | 
			
		||||
const init = async () => {
 | 
			
		||||
  ${codeText}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
init();`;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -11,44 +11,64 @@
 | 
			
		||||
    <div class="second-line"><app-amount [satoshis]="channel.capacity" digitsInfo="1.2-2"></app-amount></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
<div class="box">
 | 
			
		||||
 | 
			
		||||
  <div class="col-md">
 | 
			
		||||
    <table class="table table-borderless table-striped">
 | 
			
		||||
      <tbody>
 | 
			
		||||
        <tr>
 | 
			
		||||
          <td i18n="address.total-sent">Fee rate</td>
 | 
			
		||||
          <td>
 | 
			
		||||
            {{ channel.fee_rate ?? '-' }} <span class="symbol">ppm ({{ channel.fee_rate / 10000 | number }}%)</span>
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
          <td i18n="address.total-sent">Base fee</td>
 | 
			
		||||
          <td>
 | 
			
		||||
            <app-sats [valueOverride]="!channel.base_fee_mtokens ? '- ' : undefined" [satoshis]="channel.base_fee_mtokens / 1000" digitsInfo="1.0-2"></app-sats>
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
          <td i18n="address.total-sent">Min HTLC</td>
 | 
			
		||||
          <td>
 | 
			
		||||
            <app-sats [valueOverride]="!channel.min_htlc_mtokens ? '- ' : undefined" [satoshis]="channel.min_htlc_mtokens / 1000"></app-sats>
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
          <td i18n="address.total-sent">Max HTLC</td>
 | 
			
		||||
          <td>
 | 
			
		||||
            <app-sats [valueOverride]="!channel.max_htlc_mtokens ? '- ' : undefined" [satoshis]="channel.max_htlc_mtokens / 1000"></app-sats>
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
          <td i18n="address.total-sent">Timelock delta</td>
 | 
			
		||||
          <td>
 | 
			
		||||
            <ng-container *ngTemplateOutlet="blocksPlural; context: {$implicit: channel.cltv_delta ?? '-' }"></ng-container>
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
      </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
  </div>
 | 
			
		||||
<div class="box">
 | 
			
		||||
  <table class="table table-borderless table-striped">
 | 
			
		||||
    <tbody>
 | 
			
		||||
      <tr>
 | 
			
		||||
        <td i18n="lightning.fee-rate">Fee rate</td>
 | 
			
		||||
        <td>
 | 
			
		||||
          <span class="d-inline-block d-md-none">
 | 
			
		||||
            {{ channel.fee_rate !== null ? (channel.fee_rate | amountShortener : 2 : undefined : true) : '-' }} <span class="symbol">ppm {{ channel.fee_rate !== null ? '(' + (channel.fee_rate / 10000 | amountShortener : 2 : undefined : true) + '%)' : '' }}</span>
 | 
			
		||||
          </span>
 | 
			
		||||
          <span class="d-none d-md-inline-block">
 | 
			
		||||
            {{ channel.fee_rate !== null ? (channel.fee_rate | number) : '-' }} <span class="symbol">ppm {{ channel.fee_rate !== null ? '(' + (channel.fee_rate / 10000 | number) + '%)' : '' }}</span>
 | 
			
		||||
          </span>
 | 
			
		||||
        </td>
 | 
			
		||||
      </tr>
 | 
			
		||||
      <tr>
 | 
			
		||||
        <td i18n="lightning.base-fee">Base fee</td>
 | 
			
		||||
        <td class="base-fee">
 | 
			
		||||
          <span class="d-flex justify-content-between align-items-center">
 | 
			
		||||
            <span>
 | 
			
		||||
              <span *ngIf="channel.base_fee_mtokens !== null">
 | 
			
		||||
                {{ channel.base_fee_mtokens | amountShortener : 0 }}
 | 
			
		||||
                <span class="symbol">msats</span>
 | 
			
		||||
              </span>
 | 
			
		||||
              <span *ngIf="channel.base_fee_mtokens === null">
 | 
			
		||||
                -
 | 
			
		||||
              </span>
 | 
			
		||||
            </span>
 | 
			
		||||
            <span *ngIf="channel.base_fee_mtokens !== null" class="badge" [class]="channel.base_fee_mtokens === 0 ? 'badge-success' : 'badge-danger'"
 | 
			
		||||
              i18n-ngbTooltip="lightning.zero-base-fee"
 | 
			
		||||
              [ngbTooltip]="channel.base_fee_mtokens === 0 ? 'This channel supports zero base fee routing' :
 | 
			
		||||
                'This channel does not support zero base fee routing'"
 | 
			
		||||
              placement="bottom" i18n="lightning.zerobasefee">
 | 
			
		||||
              {{ channel.base_fee_mtokens === 0 ? 'Zero base fee' : 'Non-zero base fee' }}
 | 
			
		||||
            </span>
 | 
			
		||||
          </span>
 | 
			
		||||
        </td>
 | 
			
		||||
      </tr>
 | 
			
		||||
      <tr>
 | 
			
		||||
        <td i18n="lightning.min-htlc">Min HTLC</td>
 | 
			
		||||
        <td>
 | 
			
		||||
          <app-sats [valueOverride]="channel.min_htlc_mtokens === null ? '- ' : undefined" [satoshis]="channel.min_htlc_mtokens / 1000"></app-sats>
 | 
			
		||||
        </td>
 | 
			
		||||
      </tr>
 | 
			
		||||
      <tr>
 | 
			
		||||
        <td i18n="lightning.max-htlc">Max HTLC</td>
 | 
			
		||||
        <td>
 | 
			
		||||
          <app-sats [valueOverride]="channel.max_htlc_mtokens === null ? '- ' : undefined" [satoshis]="channel.max_htlc_mtokens / 1000"></app-sats>
 | 
			
		||||
        </td>
 | 
			
		||||
      </tr>
 | 
			
		||||
      <tr>
 | 
			
		||||
        <td i18n="lightning.timelock-detla">Timelock delta</td>
 | 
			
		||||
        <td>
 | 
			
		||||
          <ng-container *ngTemplateOutlet="blocksPlural; context: {$implicit: channel.cltv_delta ?? '-' }"></ng-container>
 | 
			
		||||
        </td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </tbody>
 | 
			
		||||
  </table>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
 | 
			
		||||
 | 
			
		||||
@ -21,4 +21,10 @@
 | 
			
		||||
  .box {
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.base-fee {
 | 
			
		||||
  @media (max-width: 768px) {
 | 
			
		||||
    padding-right: 0px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user