merge block-audit and block pages
This commit is contained in:
		
							parent
							
								
									276470474d
								
							
						
					
					
						commit
						5fc3b8b70c
					
				@ -1,3 +1,4 @@
 | 
			
		||||
import blocks from '../api/blocks';
 | 
			
		||||
import DB from '../database';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
import { BlockAudit, AuditScore } from '../mempool.interfaces';
 | 
			
		||||
@ -64,6 +65,12 @@ class BlocksAuditRepositories {
 | 
			
		||||
        rows[0].freshTxs = JSON.parse(rows[0].freshTxs);
 | 
			
		||||
        rows[0].transactions = JSON.parse(rows[0].transactions);
 | 
			
		||||
        rows[0].template = JSON.parse(rows[0].template);
 | 
			
		||||
      } else {
 | 
			
		||||
        // fallback to non-audited transaction summary
 | 
			
		||||
        const strippedTransactions = await blocks.$getStrippedBlockTransactions(hash);
 | 
			
		||||
        return {
 | 
			
		||||
          transactions: strippedTransactions
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
            
 | 
			
		||||
      return rows[0];
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,6 @@ import { AppPreloadingStrategy } from './app.preloading-strategy'
 | 
			
		||||
import { StartComponent } from './components/start/start.component';
 | 
			
		||||
import { TransactionComponent } from './components/transaction/transaction.component';
 | 
			
		||||
import { BlockComponent } from './components/block/block.component';
 | 
			
		||||
import { BlockAuditComponent } from './components/block-audit/block-audit.component';
 | 
			
		||||
import { AddressComponent } from './components/address/address.component';
 | 
			
		||||
import { MasterPageComponent } from './components/master-page/master-page.component';
 | 
			
		||||
import { AboutComponent } from './components/about/about.component';
 | 
			
		||||
@ -103,16 +102,6 @@ let routes: Routes = [
 | 
			
		||||
              },
 | 
			
		||||
            ],
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            path: 'block-audit',
 | 
			
		||||
            data: { networkSpecific: true },
 | 
			
		||||
            children: [
 | 
			
		||||
              {
 | 
			
		||||
                path: ':id',
 | 
			
		||||
                component: BlockAuditComponent,
 | 
			
		||||
              },
 | 
			
		||||
            ],
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            path: 'docs',
 | 
			
		||||
            loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule),
 | 
			
		||||
@ -219,16 +208,6 @@ let routes: Routes = [
 | 
			
		||||
              },
 | 
			
		||||
            ],
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            path: 'block-audit',
 | 
			
		||||
            data: { networkSpecific: true },
 | 
			
		||||
            children: [
 | 
			
		||||
              {
 | 
			
		||||
                path: ':id',
 | 
			
		||||
                component: BlockAuditComponent,
 | 
			
		||||
              },
 | 
			
		||||
            ],
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            path: 'docs',
 | 
			
		||||
            loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
 | 
			
		||||
@ -331,16 +310,6 @@ let routes: Routes = [
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'block-audit',
 | 
			
		||||
        data: { networkSpecific: true },
 | 
			
		||||
        children: [
 | 
			
		||||
          {
 | 
			
		||||
            path: ':id',
 | 
			
		||||
            component: BlockAuditComponent
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'docs',
 | 
			
		||||
        loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
 | 
			
		||||
 | 
			
		||||
@ -1,172 +0,0 @@
 | 
			
		||||
<div class="container-xl" (window:resize)="onResize($event)">
 | 
			
		||||
 | 
			
		||||
  <div class="title-block" id="block">
 | 
			
		||||
    <h1>
 | 
			
		||||
      <span class="next-previous-blocks">
 | 
			
		||||
        <span i18n="shared.block-audit-title">Block Audit</span>
 | 
			
		||||
         
 | 
			
		||||
        <a *ngIf="blockAudit" [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockAudit.height }}</a>
 | 
			
		||||
         
 | 
			
		||||
      </span>
 | 
			
		||||
    </h1>
 | 
			
		||||
 | 
			
		||||
    <div class="grow"></div>
 | 
			
		||||
 | 
			
		||||
    <button [routerLink]="['/block/' | relativeUrl, blockHash]" class="btn btn-sm">✕</button>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div *ngIf="!error && !isLoading">
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    <!-- OVERVIEW -->
 | 
			
		||||
    <div class="box mb-3">
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <!-- LEFT COLUMN -->
 | 
			
		||||
        <div class="col-sm">
 | 
			
		||||
          <table class="table table-borderless table-striped">
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td class="td-width" i18n="block.hash">Hash</td>
 | 
			
		||||
                <td><a [routerLink]="['/block/' | relativeUrl, blockHash]" title="{{ blockHash }}">{{ blockHash | shortenString : 13 }}</a>
 | 
			
		||||
                  <app-clipboard class="d-none d-sm-inline-block" [text]="blockHash"></app-clipboard>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="blockAudit.timestamp">Timestamp</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  ‎{{ blockAudit.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}
 | 
			
		||||
                  <div class="lg-inline">
 | 
			
		||||
                    <i class="symbol">(<app-time-since [time]="blockAudit.timestamp" [fastRender]="true">
 | 
			
		||||
                      </app-time-since>)</i>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="blockAudit.size">Size</td>
 | 
			
		||||
                <td [innerHTML]="'‎' + (blockAudit.size | bytes: 2)"></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="block.weight">Weight</td>
 | 
			
		||||
                <td [innerHTML]="'‎' + (blockAudit.weight | wuBytes: 2)"></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- RIGHT COLUMN -->
 | 
			
		||||
        <div class="col-sm" *ngIf="blockAudit">
 | 
			
		||||
          <table class="table table-borderless table-striped">
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td class="td-width" i18n="shared.transaction-count">Transactions</td>
 | 
			
		||||
                <td>{{ blockAudit.tx_count }}</td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="block.health">Block health</td>
 | 
			
		||||
                <td>{{ blockAudit.matchRate }}%</td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="block.missing-txs">Removed txs</td>
 | 
			
		||||
                <td>{{ blockAudit.missingTxs.length }}</td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="block.added-txs">Added txs</td>
 | 
			
		||||
                <td>{{ blockAudit.addedTxs.length }}</td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div> <!-- row -->
 | 
			
		||||
    </div> <!-- box -->
 | 
			
		||||
 | 
			
		||||
    <!-- ADDED vs MISSING button -->
 | 
			
		||||
    <div class="d-flex justify-content-center menu mt-3 mb-3" *ngIf="isMobile">
 | 
			
		||||
      <a class="btn btn-primary w-50 mr-1 ml-1 menu-button" [class.active]="mode === 'projected'" i18n="block.projected"
 | 
			
		||||
        fragment="projected" (click)="changeMode('projected')">Projected</a>
 | 
			
		||||
      <a class="btn btn-primary w-50 mr-1 ml-1 menu-button" [class.active]="mode === 'actual'" i18n="block.actual"
 | 
			
		||||
        fragment="actual" (click)="changeMode('actual')">Actual</a>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <ng-template [ngIf]="!error && isLoading">
 | 
			
		||||
    <!-- OVERVIEW -->
 | 
			
		||||
    <div class="box mb-3">
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <!-- LEFT COLUMN -->
 | 
			
		||||
        <div class="col-sm">
 | 
			
		||||
          <table class="table table-borderless table-striped">
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr><td class="td-width" colspan="2"><span class="skeleton-loader"></span></td></tr>
 | 
			
		||||
              <tr><td class="td-width" colspan="2"><span class="skeleton-loader"></span></td></tr>
 | 
			
		||||
              <tr><td class="td-width" colspan="2"><span class="skeleton-loader"></span></td></tr>
 | 
			
		||||
              <tr><td class="td-width" colspan="2"><span class="skeleton-loader"></span></td></tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- RIGHT COLUMN -->
 | 
			
		||||
        <div class="col-sm">
 | 
			
		||||
          <table class="table table-borderless table-striped">
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr><td class="td-width" colspan="2"><span class="skeleton-loader"></span></td></tr>
 | 
			
		||||
              <tr><td class="td-width" colspan="2"><span class="skeleton-loader"></span></td></tr>
 | 
			
		||||
              <tr><td class="td-width" colspan="2"><span class="skeleton-loader"></span></td></tr>
 | 
			
		||||
              <tr><td class="td-width" colspan="2"><span class="skeleton-loader"></span></td></tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div> <!-- row -->
 | 
			
		||||
    </div> <!-- box -->
 | 
			
		||||
 | 
			
		||||
    <!-- ADDED vs MISSING button -->
 | 
			
		||||
    <div class="d-flex justify-content-center menu mt-3 mb-3" *ngIf="isMobile">
 | 
			
		||||
      <a class="btn btn-primary w-50 mr-1 ml-1 menu-button" [class.active]="mode === 'projected'" i18n="block.projected"
 | 
			
		||||
        fragment="projected" (click)="changeMode('projected')">Projected</a>
 | 
			
		||||
      <a class="btn btn-primary w-50 mr-1 ml-1 menu-button" [class.active]="mode === 'actual'" i18n="block.actual"
 | 
			
		||||
        fragment="actual" (click)="changeMode('actual')">Actual</a>
 | 
			
		||||
    </div>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
 | 
			
		||||
  <ng-template [ngIf]="error">
 | 
			
		||||
    <div *ngIf="error && error.status === 404; else generalError" class="text-center">
 | 
			
		||||
      <br>
 | 
			
		||||
      <b i18n="error.audit-unavailable">audit unavailable</b>
 | 
			
		||||
      <br><br>
 | 
			
		||||
      <i>{{ error.error }}</i>
 | 
			
		||||
      <br>
 | 
			
		||||
      <br>
 | 
			
		||||
    </div>
 | 
			
		||||
    <ng-template #generalError>
 | 
			
		||||
      <div class="text-center">
 | 
			
		||||
        <br>
 | 
			
		||||
        <span i18n="error.general-loading-data">Error loading data.</span>
 | 
			
		||||
        <br><br>
 | 
			
		||||
        <i>{{ error }}</i>
 | 
			
		||||
        <br>
 | 
			
		||||
        <br>
 | 
			
		||||
      </div>
 | 
			
		||||
    </ng-template>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
 | 
			
		||||
  <!-- VISUALIZATIONS -->
 | 
			
		||||
  <div class="box" *ngIf="!error">
 | 
			
		||||
    <div class="row">
 | 
			
		||||
      <!-- MISSING TX RENDERING -->
 | 
			
		||||
      <div class="col-sm" *ngIf="webGlEnabled">
 | 
			
		||||
        <h3 class="block-subtitle" *ngIf="!isMobile" i18n="block.projected-block">Projected Block</h3>
 | 
			
		||||
        <app-block-overview-graph #blockGraphProjected [isLoading]="isLoading" [resolution]="75"
 | 
			
		||||
          [blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx"
 | 
			
		||||
          (txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)"></app-block-overview-graph>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <!-- ADDED TX RENDERING -->
 | 
			
		||||
      <div class="col-sm" *ngIf="webGlEnabled && !isMobile">
 | 
			
		||||
        <h3 class="block-subtitle" *ngIf="!isMobile" i18n="block.actual-block">Actual Block</h3>
 | 
			
		||||
        <app-block-overview-graph #blockGraphActual [isLoading]="isLoading" [resolution]="75"
 | 
			
		||||
          [blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx"
 | 
			
		||||
          (txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)"></app-block-overview-graph>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div> <!-- row -->
 | 
			
		||||
  </div> <!-- box -->
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
@ -1,44 +0,0 @@
 | 
			
		||||
.title-block {
 | 
			
		||||
  border-top: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table {
 | 
			
		||||
  tr td {
 | 
			
		||||
    &:last-child {
 | 
			
		||||
      text-align: right;
 | 
			
		||||
      @media (min-width: 768px) {
 | 
			
		||||
        text-align: left;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.block-tx-title {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  @media (min-width: 550px) {
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
  }
 | 
			
		||||
  h2 {
 | 
			
		||||
    line-height: 1;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    padding-bottom: 10px;
 | 
			
		||||
    @media (min-width: 550px) {
 | 
			
		||||
      padding-bottom: 0px;
 | 
			
		||||
      align-self: end;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.menu-button {
 | 
			
		||||
  @media (min-width: 768px) {
 | 
			
		||||
    max-width: 150px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.block-subtitle {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
@ -1,229 +0,0 @@
 | 
			
		||||
import { Component, OnDestroy, OnInit, AfterViewInit, ViewChildren, QueryList } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
 | 
			
		||||
import { Subscription, combineLatest, of } from 'rxjs';
 | 
			
		||||
import { map, switchMap, startWith, catchError, filter } from 'rxjs/operators';
 | 
			
		||||
import { BlockAudit, TransactionStripped } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-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({
 | 
			
		||||
  selector: 'app-block-audit',
 | 
			
		||||
  templateUrl: './block-audit.component.html',
 | 
			
		||||
  styleUrls: ['./block-audit.component.scss'],
 | 
			
		||||
  styles: [`
 | 
			
		||||
    .loadingGraphs {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 50%;
 | 
			
		||||
      left: calc(50% - 15px);
 | 
			
		||||
      z-index: 100;
 | 
			
		||||
    }
 | 
			
		||||
  `],
 | 
			
		||||
})
 | 
			
		||||
export class BlockAuditComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
			
		||||
  blockAudit: BlockAudit = undefined;
 | 
			
		||||
  transactions: string[];
 | 
			
		||||
  auditSubscription: Subscription;
 | 
			
		||||
  urlFragmentSubscription: Subscription;
 | 
			
		||||
 | 
			
		||||
  paginationMaxSize: number;
 | 
			
		||||
  page = 1;
 | 
			
		||||
  itemsPerPage: number;
 | 
			
		||||
 | 
			
		||||
  mode: 'projected' | 'actual' = 'projected';
 | 
			
		||||
  error: any;
 | 
			
		||||
  isLoading = true;
 | 
			
		||||
  webGlEnabled = true;
 | 
			
		||||
  isMobile = window.innerWidth <= 767.98;
 | 
			
		||||
  hoverTx: string;
 | 
			
		||||
 | 
			
		||||
  childChangeSubscription: Subscription;
 | 
			
		||||
 | 
			
		||||
  blockHash: string;
 | 
			
		||||
  numMissing: number = 0;
 | 
			
		||||
  numUnexpected: number = 0;
 | 
			
		||||
 | 
			
		||||
  @ViewChildren('blockGraphProjected') blockGraphProjected: QueryList<BlockOverviewGraphComponent>;
 | 
			
		||||
  @ViewChildren('blockGraphActual') blockGraphActual: QueryList<BlockOverviewGraphComponent>;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
    public stateService: StateService,
 | 
			
		||||
    private router: Router,
 | 
			
		||||
    private apiService: ApiService,
 | 
			
		||||
    private electrsApiService: ElectrsApiService,
 | 
			
		||||
  ) {
 | 
			
		||||
    this.webGlEnabled = detectWebGL();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
    this.childChangeSubscription.unsubscribe();
 | 
			
		||||
    this.urlFragmentSubscription.unsubscribe();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
 | 
			
		||||
    this.itemsPerPage = this.stateService.env.ITEMS_PER_PAGE;
 | 
			
		||||
 | 
			
		||||
    this.urlFragmentSubscription = this.route.fragment.subscribe((fragment) => {
 | 
			
		||||
      if (fragment === 'actual') {
 | 
			
		||||
        this.mode = 'actual';
 | 
			
		||||
      } else {
 | 
			
		||||
        this.mode = 'projected'
 | 
			
		||||
      }
 | 
			
		||||
      this.setupBlockGraphs();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.auditSubscription = this.route.paramMap.pipe(
 | 
			
		||||
      switchMap((params: ParamMap) => {
 | 
			
		||||
        const blockHash = params.get('id') || null;
 | 
			
		||||
        if (!blockHash) {
 | 
			
		||||
          return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let isBlockHeight = false;
 | 
			
		||||
        if (/^[0-9]+$/.test(blockHash)) {
 | 
			
		||||
          isBlockHeight = true;
 | 
			
		||||
        } else {
 | 
			
		||||
          this.blockHash = blockHash;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isBlockHeight) {
 | 
			
		||||
          return this.electrsApiService.getBlockHashFromHeight$(parseInt(blockHash, 10))
 | 
			
		||||
            .pipe(
 | 
			
		||||
              switchMap((hash: string) => {
 | 
			
		||||
                if (hash) {
 | 
			
		||||
                  this.blockHash = hash;
 | 
			
		||||
                  return this.apiService.getBlockAudit$(this.blockHash)
 | 
			
		||||
                } else {
 | 
			
		||||
                  return null;
 | 
			
		||||
                }
 | 
			
		||||
              }),
 | 
			
		||||
              catchError((err) => {
 | 
			
		||||
                this.error = err;
 | 
			
		||||
                return of(null);
 | 
			
		||||
              }),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        return this.apiService.getBlockAudit$(this.blockHash)
 | 
			
		||||
      }),
 | 
			
		||||
      filter((response) => response != null),
 | 
			
		||||
      map((response) => {
 | 
			
		||||
        const blockAudit = response.body;
 | 
			
		||||
        const inTemplate = {};
 | 
			
		||||
        const inBlock = {};
 | 
			
		||||
        const isAdded = {};
 | 
			
		||||
        const isCensored = {};
 | 
			
		||||
        const isMissing = {};
 | 
			
		||||
        const isSelected = {};
 | 
			
		||||
        this.numMissing = 0;
 | 
			
		||||
        this.numUnexpected = 0;
 | 
			
		||||
        for (const tx of blockAudit.template) {
 | 
			
		||||
          inTemplate[tx.txid] = true;
 | 
			
		||||
        }
 | 
			
		||||
        for (const tx of blockAudit.transactions) {
 | 
			
		||||
          inBlock[tx.txid] = true;
 | 
			
		||||
        }
 | 
			
		||||
        for (const txid of blockAudit.addedTxs) {
 | 
			
		||||
          isAdded[txid] = true;
 | 
			
		||||
        }
 | 
			
		||||
        for (const txid of blockAudit.missingTxs) {
 | 
			
		||||
          isCensored[txid] = true;
 | 
			
		||||
        }
 | 
			
		||||
        // set transaction statuses
 | 
			
		||||
        for (const tx of blockAudit.template) {
 | 
			
		||||
          if (isCensored[tx.txid]) {
 | 
			
		||||
            tx.status = 'censored';
 | 
			
		||||
          } else if (inBlock[tx.txid]) {
 | 
			
		||||
            tx.status = 'found';
 | 
			
		||||
          } else {
 | 
			
		||||
            tx.status = 'missing';
 | 
			
		||||
            isMissing[tx.txid] = true;
 | 
			
		||||
            this.numMissing++;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        for (const [index, tx] of blockAudit.transactions.entries()) {
 | 
			
		||||
          if (index === 0) {
 | 
			
		||||
            tx.status = null;
 | 
			
		||||
          } else if (isAdded[tx.txid]) {
 | 
			
		||||
            tx.status = 'added';
 | 
			
		||||
          } else if (inTemplate[tx.txid]) {
 | 
			
		||||
            tx.status = 'found';
 | 
			
		||||
          } else {
 | 
			
		||||
            tx.status = 'selected';
 | 
			
		||||
            isSelected[tx.txid] = true;
 | 
			
		||||
            this.numUnexpected++;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        for (const tx of blockAudit.transactions) {
 | 
			
		||||
          inBlock[tx.txid] = true;
 | 
			
		||||
        }
 | 
			
		||||
        return blockAudit;
 | 
			
		||||
      }),
 | 
			
		||||
      catchError((err) => {
 | 
			
		||||
        console.log(err);
 | 
			
		||||
        this.error = err;
 | 
			
		||||
        this.isLoading = false;
 | 
			
		||||
        return of(null);
 | 
			
		||||
      }),
 | 
			
		||||
    ).subscribe((blockAudit) => {
 | 
			
		||||
      this.blockAudit = blockAudit;
 | 
			
		||||
      this.setupBlockGraphs();
 | 
			
		||||
      this.isLoading = false;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngAfterViewInit() {
 | 
			
		||||
    this.childChangeSubscription = combineLatest([this.blockGraphProjected.changes.pipe(startWith(null)), this.blockGraphActual.changes.pipe(startWith(null))]).subscribe(() => {
 | 
			
		||||
      this.setupBlockGraphs();
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setupBlockGraphs() {
 | 
			
		||||
    if (this.blockAudit) {
 | 
			
		||||
      this.blockGraphProjected.forEach(graph => {
 | 
			
		||||
        graph.destroy();
 | 
			
		||||
        if (this.isMobile && this.mode === 'actual') {
 | 
			
		||||
          graph.setup(this.blockAudit.transactions);
 | 
			
		||||
        } else {
 | 
			
		||||
          graph.setup(this.blockAudit.template);
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      this.blockGraphActual.forEach(graph => {
 | 
			
		||||
        graph.destroy();
 | 
			
		||||
        graph.setup(this.blockAudit.transactions);
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onResize(event: any) {
 | 
			
		||||
    const isMobile = event.target.innerWidth <= 767.98;
 | 
			
		||||
    const changed = isMobile !== this.isMobile;
 | 
			
		||||
    this.isMobile = isMobile;
 | 
			
		||||
    this.paginationMaxSize = event.target.innerWidth < 670 ? 3 : 5;
 | 
			
		||||
 | 
			
		||||
    if (changed) {
 | 
			
		||||
      this.changeMode(this.mode);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  changeMode(mode: 'projected' | 'actual') {
 | 
			
		||||
    this.router.navigate([], { fragment: mode });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onTxClick(event: TransactionStripped): void {
 | 
			
		||||
    const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.txid}`);
 | 
			
		||||
    this.router.navigate([url]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onTxHover(txid: string): void {
 | 
			
		||||
    if (txid && txid.length) {
 | 
			
		||||
      this.hoverTx = txid;
 | 
			
		||||
    } else {
 | 
			
		||||
      this.hoverTx = null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +1,8 @@
 | 
			
		||||
<div class="block-overview-graph">
 | 
			
		||||
  <canvas class="block-overview-canvas" [class.clickable]="!!hoverTx" #blockCanvas></canvas>
 | 
			
		||||
  <div class="loader-wrapper" [class.hidden]="!isLoading || disableSpinner">
 | 
			
		||||
    <div class="spinner-border ml-3 loading" role="status"></div>
 | 
			
		||||
  <div class="loader-wrapper" [class.hidden]="(!isLoading || disableSpinner) && !unavailable">
 | 
			
		||||
    <div *ngIf="isLoading" class="spinner-border ml-3 loading" role="status"></div>
 | 
			
		||||
    <div *ngIf="!isLoading && unavailable" class="ml-3" i18n="block.not-available">not available</div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <app-block-overview-tooltip
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
 | 
			
		||||
  @Input() flip = true;
 | 
			
		||||
  @Input() disableSpinner = false;
 | 
			
		||||
  @Input() mirrorTxid: string | void;
 | 
			
		||||
  @Input() unavailable: boolean = false;
 | 
			
		||||
  @Output() txClickEvent = new EventEmitter<TransactionStripped>();
 | 
			
		||||
  @Output() txHoverEvent = new EventEmitter<string>();
 | 
			
		||||
  @Output() readyEvent = new EventEmitter();
 | 
			
		||||
 | 
			
		||||
@ -3,12 +3,13 @@ import { FastVertexArray } from './fast-vertex-array';
 | 
			
		||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
 | 
			
		||||
import { SpriteUpdateParams, Square, Color, ViewUpdateParams } from './sprite-types';
 | 
			
		||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
 | 
			
		||||
import BlockScene from './block-scene';
 | 
			
		||||
 | 
			
		||||
const hoverTransitionTime = 300;
 | 
			
		||||
const defaultHoverColor = hexToColor('1bd8f4');
 | 
			
		||||
 | 
			
		||||
const feeColors = mempoolFeeColors.map(hexToColor);
 | 
			
		||||
const auditFeeColors = feeColors.map((color) => desaturate(color, 0.3));
 | 
			
		||||
const auditFeeColors = feeColors.map((color) => darken(desaturate(color, 0.3), 0.9));
 | 
			
		||||
const auditColors = {
 | 
			
		||||
  censored: hexToColor('f344df'),
 | 
			
		||||
  missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
 | 
			
		||||
@ -34,7 +35,8 @@ export default class TxView implements TransactionStripped {
 | 
			
		||||
  vsize: number;
 | 
			
		||||
  value: number;
 | 
			
		||||
  feerate: number;
 | 
			
		||||
  status?: 'found' | 'missing' | 'added' | 'censored' | 'selected';
 | 
			
		||||
  status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
 | 
			
		||||
  context?: 'projected' | 'actual';
 | 
			
		||||
 | 
			
		||||
  initialised: boolean;
 | 
			
		||||
  vertexArray: FastVertexArray;
 | 
			
		||||
@ -48,6 +50,7 @@ export default class TxView implements TransactionStripped {
 | 
			
		||||
  dirty: boolean;
 | 
			
		||||
 | 
			
		||||
  constructor(tx: TransactionStripped, vertexArray: FastVertexArray) {
 | 
			
		||||
    this.context = tx.context;
 | 
			
		||||
    this.txid = tx.txid;
 | 
			
		||||
    this.fee = tx.fee;
 | 
			
		||||
    this.vsize = tx.vsize;
 | 
			
		||||
@ -159,12 +162,18 @@ export default class TxView implements TransactionStripped {
 | 
			
		||||
        return auditColors.censored;
 | 
			
		||||
      case 'missing':
 | 
			
		||||
        return auditColors.missing;
 | 
			
		||||
      case 'fresh':
 | 
			
		||||
        return auditColors.missing;
 | 
			
		||||
      case 'added':
 | 
			
		||||
        return auditColors.added;
 | 
			
		||||
      case 'selected':
 | 
			
		||||
        return auditColors.selected;
 | 
			
		||||
      case 'found':
 | 
			
		||||
        return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
 | 
			
		||||
        if (this.context === 'projected') {
 | 
			
		||||
          return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
 | 
			
		||||
        } else {
 | 
			
		||||
          return feeLevelColor;
 | 
			
		||||
        }
 | 
			
		||||
      default:
 | 
			
		||||
        return feeLevelColor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -37,9 +37,10 @@
 | 
			
		||||
        <ng-container [ngSwitch]="tx?.status">
 | 
			
		||||
          <td *ngSwitchCase="'found'" i18n="transaction.audit.match">match</td>
 | 
			
		||||
          <td *ngSwitchCase="'censored'" i18n="transaction.audit.removed">removed</td>
 | 
			
		||||
          <td *ngSwitchCase="'missing'" i18n="transaction.audit.omitted">omitted</td>
 | 
			
		||||
          <td *ngSwitchCase="'missing'" i18n="transaction.audit.marginal">marginal fee rate</td>
 | 
			
		||||
          <td *ngSwitchCase="'fresh'" i18n="transaction.audit.recently-broadcast">recently broadcast</td>
 | 
			
		||||
          <td *ngSwitchCase="'added'" i18n="transaction.audit.added">added</td>
 | 
			
		||||
          <td *ngSwitchCase="'selected'" i18n="transaction.audit.extra">extra</td>
 | 
			
		||||
          <td *ngSwitchCase="'selected'" i18n="transaction.audit.marginal">marginal fee rate</td>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </tbody>
 | 
			
		||||
 | 
			
		||||
@ -54,7 +54,19 @@
 | 
			
		||||
                <td i18n="block.weight">Weight</td>
 | 
			
		||||
                <td [innerHTML]="'‎' + (block.weight | wuBytes: 2)"></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <ng-template [ngIf]="webGlEnabled">
 | 
			
		||||
              <tr *ngIf="auditEnabled">
 | 
			
		||||
                <td i18n="block.health">Block health</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  <span *ngIf="blockAudit?.matchRate != null">{{ blockAudit.matchRate }}%</span>
 | 
			
		||||
                  <span *ngIf="blockAudit?.matchRate === null" i18n="unknown">Unknown</span>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <ng-container *ngIf="!indexingAvailable && webGlEnabled">
 | 
			
		||||
                <tr *ngIf="isMobile && auditEnabled"></tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td i18n="mempool-block.fee-span">Fee span</td>
 | 
			
		||||
                  <td><span>{{ block.extras.feeRange[0] | number:'1.0-0' }} - {{ block.extras.feeRange[block.extras.feeRange.length - 1] | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></span></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr *ngIf="block?.extras?.medianFee != undefined">
 | 
			
		||||
                  <td class="td-width" i18n="block.median-fee">Median fee</td>
 | 
			
		||||
                  <td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
 | 
			
		||||
@ -98,26 +110,19 @@
 | 
			
		||||
                <tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
 | 
			
		||||
                  <td i18n="block.miner">Miner</td>
 | 
			
		||||
                  <td *ngIf="stateService.env.MINING_DASHBOARD">
 | 
			
		||||
                    <a [attr.data-cy]="'block-details-miner-badge'" placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge"
 | 
			
		||||
                    <a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge"
 | 
			
		||||
                      [class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
 | 
			
		||||
                      {{ block.extras.pool.name }}
 | 
			
		||||
                    </a>
 | 
			
		||||
                  </td>
 | 
			
		||||
                  <td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
 | 
			
		||||
                    <span [attr.data-cy]="'block-details-miner-badge'" placement="bottom" class="badge"
 | 
			
		||||
                    <span placement="bottom" class="badge"
 | 
			
		||||
                      [class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
 | 
			
		||||
                      {{ block.extras.pool.name }}
 | 
			
		||||
                  </span>
 | 
			
		||||
                  </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr *ngIf="indexingAvailable">
 | 
			
		||||
                  <td i18n="block.health">Block health</td>
 | 
			
		||||
                  <td>
 | 
			
		||||
                    <a *ngIf="block.extras?.matchRate != null" [routerLink]="['/block-audit/' | relativeUrl, blockHash]">{{ block.extras.matchRate }}%</a>
 | 
			
		||||
                    <span *ngIf="block.extras?.matchRate === null" i18n="unknown">Unknown</span>
 | 
			
		||||
                  </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </ng-template>
 | 
			
		||||
              </ng-container>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
@ -138,7 +143,11 @@
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td colspan="2"><span class="skeleton-loader"></span></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <ng-template [ngIf]="webGlEnabled">
 | 
			
		||||
              <tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
 | 
			
		||||
                <td colspan="2"><span class="skeleton-loader"></span></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <ng-container *ngIf="!indexingAvailable && webGlEnabled">
 | 
			
		||||
                <tr *ngIf="isMobile && !auditEnabled"></tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
@ -148,17 +157,25 @@
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td colspan="2"><span class="skeleton-loader"></span></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                <tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
 | 
			
		||||
                  <td colspan="2"><span class="skeleton-loader"></span></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </ng-template>
 | 
			
		||||
                <tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
 | 
			
		||||
                  <td colspan="2"><span class="skeleton-loader"></span></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </ng-container>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
      </ng-template>
 | 
			
		||||
      <div class="col-sm" *ngIf="!webGlEnabled">
 | 
			
		||||
        <table class="table table-borderless table-striped" *ngIf="!isLoadingBlock">
 | 
			
		||||
      <div class="col-sm">
 | 
			
		||||
        <table class="table table-borderless table-striped" *ngIf="!isLoadingBlock && (indexingAvailable || !webGlEnabled)">
 | 
			
		||||
          <tbody>
 | 
			
		||||
            <tr *ngIf="isMobile && auditEnabled"></tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td i18n="mempool-block.fee-span">Fee span</td>
 | 
			
		||||
              <td><span>{{ block.extras.feeRange[0] | number:'1.0-0' }} - {{ block.extras.feeRange[block.extras.feeRange.length - 1] | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></span></td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr *ngIf="block?.extras?.medianFee != undefined">
 | 
			
		||||
              <td class="td-width" i18n="block.median-fee">Median fee</td>
 | 
			
		||||
              <td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
 | 
			
		||||
@ -216,8 +233,9 @@
 | 
			
		||||
            </tr>
 | 
			
		||||
          </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
        <table class="table table-borderless table-striped" *ngIf="isLoadingBlock">
 | 
			
		||||
        <table class="table table-borderless table-striped" *ngIf="isLoadingBlock && (indexingAvailable || !webGlEnabled)">
 | 
			
		||||
          <tbody>
 | 
			
		||||
            <tr *ngIf="isMobile && !auditEnabled"></tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
 | 
			
		||||
            </tr>
 | 
			
		||||
@ -230,22 +248,54 @@
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td colspan="2"><span class="skeleton-loader"></span></td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
 | 
			
		||||
              <td colspan="2"><span class="skeleton-loader"></span></td>
 | 
			
		||||
            </tr>
 | 
			
		||||
          </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="col-sm chart-container" *ngIf="webGlEnabled">
 | 
			
		||||
        <app-block-overview-graph
 | 
			
		||||
          #blockGraph
 | 
			
		||||
          [isLoading]="isLoadingOverview"
 | 
			
		||||
          [resolution]="75"
 | 
			
		||||
          [blockLimit]="stateService.blockVSize"
 | 
			
		||||
          [orientation]="'top'"
 | 
			
		||||
          [flip]="false"
 | 
			
		||||
          (txClickEvent)="onTxClick($event)"
 | 
			
		||||
        ></app-block-overview-graph>
 | 
			
		||||
        <div class="col-sm chart-container" *ngIf="webGlEnabled && !indexingAvailable">
 | 
			
		||||
          <app-block-overview-graph
 | 
			
		||||
            #blockGraphActual
 | 
			
		||||
            [isLoading]="isLoadingOverview"
 | 
			
		||||
            [resolution]="75"
 | 
			
		||||
            [blockLimit]="stateService.blockVSize"
 | 
			
		||||
            [orientation]="'top'"
 | 
			
		||||
            [flip]="false"
 | 
			
		||||
            (txClickEvent)="onTxClick($event)"
 | 
			
		||||
          ></app-block-overview-graph>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <span id="overview"></span>
 | 
			
		||||
 | 
			
		||||
  <br>
 | 
			
		||||
 | 
			
		||||
  <!-- VISUALIZATIONS -->
 | 
			
		||||
  <div class="box" *ngIf="!error && webGlEnabled && indexingAvailable">
 | 
			
		||||
    <div class="nav nav-tabs" *ngIf="isMobile && auditEnabled">
 | 
			
		||||
      <a class="nav-link" [class.active]="mode === 'projected'" i18n="block.projected"
 | 
			
		||||
        fragment="projected" (click)="changeMode('projected')">Projected</a>
 | 
			
		||||
      <a class="nav-link" [class.active]="mode === 'actual'" i18n="block.actual"
 | 
			
		||||
        fragment="actual" (click)="changeMode('actual')">Actual</a>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="row">
 | 
			
		||||
      <div class="col-sm">
 | 
			
		||||
        <h3 class="block-subtitle" *ngIf="!isMobile" i18n="block.projected-block">Projected Block</h3>
 | 
			
		||||
        <app-block-overview-graph #blockGraphProjected [isLoading]="isLoadingOverview" [resolution]="75"
 | 
			
		||||
          [blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx"
 | 
			
		||||
          (txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="!auditEnabled"></app-block-overview-graph> 
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="col-sm" *ngIf="!isMobile">
 | 
			
		||||
        <h3 class="block-subtitle" *ngIf="!isMobile" i18n="block.actual-block">Actual Block</h3>
 | 
			
		||||
        <app-block-overview-graph #blockGraphActual [isLoading]="isLoadingOverview" [resolution]="75"
 | 
			
		||||
          [blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined"
 | 
			
		||||
          (txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)"></app-block-overview-graph>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  
 | 
			
		||||
  <ng-template [ngIf]="!isLoadingBlock && !error">
 | 
			
		||||
    <div [hidden]="!showDetails" id="details">
 | 
			
		||||
      <br>
 | 
			
		||||
@ -273,6 +323,7 @@
 | 
			
		||||
          <div class="col-sm" *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
 | 
			
		||||
            <table class="table table-borderless table-striped">
 | 
			
		||||
              <tbody>
 | 
			
		||||
                <tr *ngIf="isMobile"></tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td class="td-width" i18n="block.difficulty">Difficulty</td>
 | 
			
		||||
                  <td>{{ block.difficulty }}</td>
 | 
			
		||||
 | 
			
		||||
@ -171,3 +171,35 @@ h1 {
 | 
			
		||||
    margin: auto;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.menu-button {
 | 
			
		||||
  @media (min-width: 768px) {
 | 
			
		||||
    max-width: 150px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.block-subtitle {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav-tabs {
 | 
			
		||||
  border-color: white;
 | 
			
		||||
  border-width: 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav-tabs .nav-link {
 | 
			
		||||
  background: inherit;
 | 
			
		||||
  border-width: 1px;
 | 
			
		||||
  border-bottom: none;
 | 
			
		||||
  border-color: transparent;
 | 
			
		||||
  margin-bottom: -1px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
 | 
			
		||||
  &.active {
 | 
			
		||||
    background: #24273e;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.active, &:hover {
 | 
			
		||||
    border-color: white;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,15 +1,15 @@
 | 
			
		||||
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
 | 
			
		||||
import { Component, OnInit, OnDestroy, ViewChildren, QueryList } from '@angular/core';
 | 
			
		||||
import { Location } from '@angular/common';
 | 
			
		||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, pairwise } from 'rxjs/operators';
 | 
			
		||||
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, pairwise, filter } from 'rxjs/operators';
 | 
			
		||||
import { Transaction, Vout } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { Observable, of, Subscription, asyncScheduler, EMPTY, Subject } from 'rxjs';
 | 
			
		||||
import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest } from 'rxjs';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
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 { BlockAudit, 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';
 | 
			
		||||
@ -17,11 +17,20 @@ import { detectWebGL } from '../../shared/graphs.utils';
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-block',
 | 
			
		||||
  templateUrl: './block.component.html',
 | 
			
		||||
  styleUrls: ['./block.component.scss']
 | 
			
		||||
  styleUrls: ['./block.component.scss'],
 | 
			
		||||
  styles: [`
 | 
			
		||||
    .loadingGraphs {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 50%;
 | 
			
		||||
      left: calc(50% - 15px);
 | 
			
		||||
      z-index: 100;
 | 
			
		||||
    }
 | 
			
		||||
  `],
 | 
			
		||||
})
 | 
			
		||||
export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
  network = '';
 | 
			
		||||
  block: BlockExtended;
 | 
			
		||||
  blockAudit: BlockAudit = undefined;
 | 
			
		||||
  blockHeight: number;
 | 
			
		||||
  lastBlockHeight: number;
 | 
			
		||||
  nextBlockHeight: number;
 | 
			
		||||
@ -48,9 +57,16 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
  overviewError: any = null;
 | 
			
		||||
  webGlEnabled = true;
 | 
			
		||||
  indexingAvailable = false;
 | 
			
		||||
  auditEnabled = true;
 | 
			
		||||
  isMobile = window.innerWidth <= 767.98;
 | 
			
		||||
  hoverTx: string;
 | 
			
		||||
  numMissing: number = 0;
 | 
			
		||||
  numUnexpected: number = 0;
 | 
			
		||||
  mode: 'projected' | 'actual' = 'projected';
 | 
			
		||||
 | 
			
		||||
  transactionSubscription: Subscription;
 | 
			
		||||
  overviewSubscription: Subscription;
 | 
			
		||||
  auditSubscription: Subscription;
 | 
			
		||||
  keyNavigationSubscription: Subscription;
 | 
			
		||||
  blocksSubscription: Subscription;
 | 
			
		||||
  networkChangedSubscription: Subscription;
 | 
			
		||||
@ -60,10 +76,10 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
  nextBlockTxListSubscription: Subscription = undefined;
 | 
			
		||||
  timeLtrSubscription: Subscription;
 | 
			
		||||
  timeLtr: boolean;
 | 
			
		||||
  fetchAuditScore$ = new Subject<string>();
 | 
			
		||||
  fetchAuditScoreSubscription: Subscription;
 | 
			
		||||
  childChangeSubscription: Subscription;
 | 
			
		||||
 | 
			
		||||
  @ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent;
 | 
			
		||||
  @ViewChildren('blockGraphProjected') blockGraphProjected: QueryList<BlockOverviewGraphComponent>;
 | 
			
		||||
  @ViewChildren('blockGraphActual') blockGraphActual: QueryList<BlockOverviewGraphComponent>;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
@ -89,8 +105,8 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
      this.timeLtr = !!ltr;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.indexingAvailable = (this.stateService.env.BASE_MODULE === 'mempool' &&
 | 
			
		||||
      this.stateService.env.MINING_DASHBOARD === true);
 | 
			
		||||
    this.indexingAvailable = (this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true);
 | 
			
		||||
    this.auditEnabled = this.indexingAvailable;
 | 
			
		||||
 | 
			
		||||
    this.txsLoadingStatus$ = this.route.paramMap
 | 
			
		||||
      .pipe(
 | 
			
		||||
@ -107,30 +123,12 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
        if (block.id === this.blockHash) {
 | 
			
		||||
          this.block = block;
 | 
			
		||||
          if (this.block.id && this.block?.extras?.matchRate == null) {
 | 
			
		||||
            this.fetchAuditScore$.next(this.block.id);
 | 
			
		||||
          }
 | 
			
		||||
          if (block?.extras?.reward != undefined) {
 | 
			
		||||
            this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    if (this.indexingAvailable) {
 | 
			
		||||
      this.fetchAuditScoreSubscription = this.fetchAuditScore$
 | 
			
		||||
        .pipe(
 | 
			
		||||
          switchMap((hash) => this.apiService.getBlockAuditScore$(hash)),
 | 
			
		||||
          catchError(() => EMPTY),
 | 
			
		||||
        )
 | 
			
		||||
        .subscribe((score) => {
 | 
			
		||||
          if (score && score.hash === this.block.id) {
 | 
			
		||||
            this.block.extras.matchRate = score.matchRate || null;
 | 
			
		||||
          } else {
 | 
			
		||||
            this.block.extras.matchRate = null;
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const block$ = this.route.paramMap.pipe(
 | 
			
		||||
      switchMap((params: ParamMap) => {
 | 
			
		||||
        const blockHash: string = params.get('id') || '';
 | 
			
		||||
@ -212,7 +210,11 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
          setTimeout(() => {
 | 
			
		||||
            this.nextBlockSubscription = this.apiService.getBlock$(block.previousblockhash).subscribe();
 | 
			
		||||
            this.nextBlockTxListSubscription = this.electrsApiService.getBlockTransactions$(block.previousblockhash).subscribe();
 | 
			
		||||
            this.nextBlockSummarySubscription = this.apiService.getStrippedBlockTransactions$(block.previousblockhash).subscribe();
 | 
			
		||||
            if (this.indexingAvailable) {
 | 
			
		||||
              this.apiService.getBlockAudit$(block.previousblockhash);
 | 
			
		||||
            } else {
 | 
			
		||||
              this.nextBlockSummarySubscription = this.apiService.getStrippedBlockTransactions$(block.previousblockhash).subscribe();
 | 
			
		||||
            }
 | 
			
		||||
          }, 100);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -229,9 +231,6 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
          this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
 | 
			
		||||
        }
 | 
			
		||||
        this.stateService.markBlock$.next({ blockHeight: this.blockHeight });
 | 
			
		||||
        if (this.block.id && this.block?.extras?.matchRate == null) {
 | 
			
		||||
          this.fetchAuditScore$.next(this.block.id);
 | 
			
		||||
        }
 | 
			
		||||
        this.isLoadingTransactions = true;
 | 
			
		||||
        this.transactions = null;
 | 
			
		||||
        this.transactionsError = null;
 | 
			
		||||
@ -263,40 +262,126 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
      this.isLoadingOverview = false;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.overviewSubscription = block$.pipe(
 | 
			
		||||
      startWith(null),
 | 
			
		||||
      pairwise(),
 | 
			
		||||
      switchMap(([prevBlock, block]) => this.apiService.getStrippedBlockTransactions$(block.id)
 | 
			
		||||
        .pipe(
 | 
			
		||||
          catchError((err) => {
 | 
			
		||||
            this.overviewError = err;
 | 
			
		||||
            return of([]);
 | 
			
		||||
          }),
 | 
			
		||||
          switchMap((transactions) => {
 | 
			
		||||
            if (prevBlock) {
 | 
			
		||||
              return of({ transactions, direction: (prevBlock.height < block.height) ? 'right' : 'left' });
 | 
			
		||||
            } else {
 | 
			
		||||
              return of({ transactions, direction: 'down' });
 | 
			
		||||
    if (!this.indexingAvailable) {
 | 
			
		||||
      this.overviewSubscription = block$.pipe(
 | 
			
		||||
        startWith(null),
 | 
			
		||||
        pairwise(),
 | 
			
		||||
        switchMap(([prevBlock, block]) => this.apiService.getStrippedBlockTransactions$(block.id)
 | 
			
		||||
          .pipe(
 | 
			
		||||
            catchError((err) => {
 | 
			
		||||
              this.overviewError = err;
 | 
			
		||||
              return of([]);
 | 
			
		||||
            }),
 | 
			
		||||
            switchMap((transactions) => {
 | 
			
		||||
              if (prevBlock) {
 | 
			
		||||
                return of({ transactions, direction: (prevBlock.height < block.height) ? 'right' : 'left' });
 | 
			
		||||
              } else {
 | 
			
		||||
                return of({ transactions, direction: 'down' });
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
          )
 | 
			
		||||
        ),
 | 
			
		||||
      )
 | 
			
		||||
      .subscribe(({transactions, direction}: {transactions: TransactionStripped[], direction: string}) => {
 | 
			
		||||
        this.strippedTransactions = transactions;
 | 
			
		||||
        this.isLoadingOverview = false;
 | 
			
		||||
        this.setupBlockGraphs();
 | 
			
		||||
      },
 | 
			
		||||
      (error) => {
 | 
			
		||||
        this.error = error;
 | 
			
		||||
        this.isLoadingOverview = false;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.indexingAvailable) {
 | 
			
		||||
      this.auditSubscription = block$.pipe(
 | 
			
		||||
        startWith(null),
 | 
			
		||||
        pairwise(),
 | 
			
		||||
        switchMap(([prevBlock, block]) => this.apiService.getBlockAudit$(block.id)
 | 
			
		||||
          .pipe(
 | 
			
		||||
            catchError((err) => {
 | 
			
		||||
              this.overviewError = err;
 | 
			
		||||
              return of([]);
 | 
			
		||||
            })
 | 
			
		||||
          )
 | 
			
		||||
        ),
 | 
			
		||||
        filter((response) => response != null),
 | 
			
		||||
        map((response) => {
 | 
			
		||||
          const blockAudit = response.body;
 | 
			
		||||
          const inTemplate = {};
 | 
			
		||||
          const inBlock = {};
 | 
			
		||||
          const isAdded = {};
 | 
			
		||||
          const isCensored = {};
 | 
			
		||||
          const isMissing = {};
 | 
			
		||||
          const isSelected = {};
 | 
			
		||||
          const isFresh = {};
 | 
			
		||||
          this.numMissing = 0;
 | 
			
		||||
          this.numUnexpected = 0;
 | 
			
		||||
 | 
			
		||||
          if (blockAudit.template) {
 | 
			
		||||
            for (const tx of blockAudit.template) {
 | 
			
		||||
              inTemplate[tx.txid] = true;
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
        )
 | 
			
		||||
      ),
 | 
			
		||||
    )
 | 
			
		||||
    .subscribe(({transactions, direction}: {transactions: TransactionStripped[], direction: string}) => {
 | 
			
		||||
      this.strippedTransactions = transactions;
 | 
			
		||||
      this.isLoadingOverview = false;
 | 
			
		||||
      if (this.blockGraph) {
 | 
			
		||||
        this.blockGraph.destroy();
 | 
			
		||||
        this.blockGraph.setup(this.strippedTransactions);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    (error) => {
 | 
			
		||||
      this.error = error;
 | 
			
		||||
      this.isLoadingOverview = false;
 | 
			
		||||
      if (this.blockGraph) {
 | 
			
		||||
        this.blockGraph.destroy();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
            for (const tx of blockAudit.transactions) {
 | 
			
		||||
              inBlock[tx.txid] = true;
 | 
			
		||||
            }
 | 
			
		||||
            for (const txid of blockAudit.addedTxs) {
 | 
			
		||||
              isAdded[txid] = true;
 | 
			
		||||
            }
 | 
			
		||||
            for (const txid of blockAudit.missingTxs) {
 | 
			
		||||
              isCensored[txid] = true;
 | 
			
		||||
            }
 | 
			
		||||
            for (const txid of blockAudit.freshTxs || []) {
 | 
			
		||||
              isFresh[txid] = true;
 | 
			
		||||
            }
 | 
			
		||||
            // set transaction statuses
 | 
			
		||||
            for (const tx of blockAudit.template) {
 | 
			
		||||
              tx.context = 'projected';
 | 
			
		||||
              if (isCensored[tx.txid]) {
 | 
			
		||||
                tx.status = 'censored';
 | 
			
		||||
              } else if (inBlock[tx.txid]) {
 | 
			
		||||
                tx.status = 'found';
 | 
			
		||||
              } else {
 | 
			
		||||
                tx.status = isFresh[tx.txid] ? 'fresh' : 'missing';
 | 
			
		||||
                isMissing[tx.txid] = true;
 | 
			
		||||
                this.numMissing++;
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            for (const [index, tx] of blockAudit.transactions.entries()) {
 | 
			
		||||
              tx.context = 'actual';
 | 
			
		||||
              if (index === 0) {
 | 
			
		||||
                tx.status = null;
 | 
			
		||||
              } else if (isAdded[tx.txid]) {
 | 
			
		||||
                tx.status = 'added';
 | 
			
		||||
              } else if (inTemplate[tx.txid]) {
 | 
			
		||||
                tx.status = 'found';
 | 
			
		||||
              } else {
 | 
			
		||||
                tx.status = 'selected';
 | 
			
		||||
                isSelected[tx.txid] = true;
 | 
			
		||||
                this.numUnexpected++;
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            for (const tx of blockAudit.transactions) {
 | 
			
		||||
              inBlock[tx.txid] = true;
 | 
			
		||||
            }
 | 
			
		||||
            this.auditEnabled = true;
 | 
			
		||||
          } else {
 | 
			
		||||
            this.auditEnabled = false;
 | 
			
		||||
          }
 | 
			
		||||
          return blockAudit;
 | 
			
		||||
        }),
 | 
			
		||||
        catchError((err) => {
 | 
			
		||||
          console.log(err);
 | 
			
		||||
          this.error = err;
 | 
			
		||||
          this.isLoadingOverview = false;
 | 
			
		||||
          return of(null);
 | 
			
		||||
        }),
 | 
			
		||||
      ).subscribe((blockAudit) => {
 | 
			
		||||
        this.blockAudit = blockAudit;
 | 
			
		||||
        this.setupBlockGraphs();
 | 
			
		||||
        this.isLoadingOverview = false;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.networkChangedSubscription = this.stateService.networkChanged$
 | 
			
		||||
      .subscribe((network) => this.network = network);
 | 
			
		||||
@ -307,6 +392,12 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
      } else {
 | 
			
		||||
        this.showDetails = false;
 | 
			
		||||
      }
 | 
			
		||||
      if (params.view === 'projected') {
 | 
			
		||||
        this.mode = 'projected';
 | 
			
		||||
      } else {
 | 
			
		||||
        this.mode = 'actual';
 | 
			
		||||
      }
 | 
			
		||||
      this.setupBlockGraphs();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.keyNavigationSubscription = this.stateService.keyNavigation$.subscribe((event) => {
 | 
			
		||||
@ -325,17 +416,24 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngAfterViewInit(): void {
 | 
			
		||||
    this.childChangeSubscription = combineLatest([this.blockGraphProjected.changes.pipe(startWith(null)), this.blockGraphActual.changes.pipe(startWith(null))]).subscribe(() => {
 | 
			
		||||
      this.setupBlockGraphs();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
    this.stateService.markBlock$.next({});
 | 
			
		||||
    this.transactionSubscription.unsubscribe();
 | 
			
		||||
    this.overviewSubscription.unsubscribe();
 | 
			
		||||
    this.overviewSubscription?.unsubscribe();
 | 
			
		||||
    this.auditSubscription?.unsubscribe();
 | 
			
		||||
    this.keyNavigationSubscription.unsubscribe();
 | 
			
		||||
    this.blocksSubscription.unsubscribe();
 | 
			
		||||
    this.networkChangedSubscription.unsubscribe();
 | 
			
		||||
    this.queryParamsSubscription.unsubscribe();
 | 
			
		||||
    this.timeLtrSubscription.unsubscribe();
 | 
			
		||||
    this.fetchAuditScoreSubscription?.unsubscribe();
 | 
			
		||||
    this.unsubscribeNextBlockSubscriptions();
 | 
			
		||||
    this.childChangeSubscription.unsubscribe();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  unsubscribeNextBlockSubscriptions() {
 | 
			
		||||
@ -382,7 +480,7 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
      this.showDetails = false;
 | 
			
		||||
      this.router.navigate([], {
 | 
			
		||||
        relativeTo: this.route,
 | 
			
		||||
        queryParams: { showDetails: false },
 | 
			
		||||
        queryParams: { showDetails: false, view: this.mode },
 | 
			
		||||
        queryParamsHandling: 'merge',
 | 
			
		||||
        fragment: 'block'
 | 
			
		||||
      });
 | 
			
		||||
@ -390,7 +488,7 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
      this.showDetails = true;
 | 
			
		||||
      this.router.navigate([], {
 | 
			
		||||
        relativeTo: this.route,
 | 
			
		||||
        queryParams: { showDetails: true },
 | 
			
		||||
        queryParams: { showDetails: true, view: this.mode },
 | 
			
		||||
        queryParamsHandling: 'merge',
 | 
			
		||||
        fragment: 'details'
 | 
			
		||||
      });
 | 
			
		||||
@ -409,10 +507,6 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
    return this.block && this.block.height > 681393 && (new Date().getTime() / 1000) < 1628640000;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onResize(event: any) {
 | 
			
		||||
    this.paginationMaxSize = event.target.innerWidth < 670 ? 3 : 5;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  navigateToPreviousBlock() {
 | 
			
		||||
    if (!this.block) {
 | 
			
		||||
      return;
 | 
			
		||||
@ -443,8 +537,53 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setupBlockGraphs(): void {
 | 
			
		||||
    if (this.blockAudit || this.strippedTransactions) {
 | 
			
		||||
      this.blockGraphProjected.forEach(graph => {
 | 
			
		||||
        graph.destroy();
 | 
			
		||||
        if (this.isMobile && this.mode === 'actual') {
 | 
			
		||||
          graph.setup(this.blockAudit?.transactions || this.strippedTransactions ||  []);
 | 
			
		||||
        } else {
 | 
			
		||||
          graph.setup(this.blockAudit?.template || []);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      this.blockGraphActual.forEach(graph => {
 | 
			
		||||
        graph.destroy();
 | 
			
		||||
        graph.setup(this.blockAudit?.transactions || this.strippedTransactions || []);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onResize(event: any): void {
 | 
			
		||||
    const isMobile = event.target.innerWidth <= 767.98;
 | 
			
		||||
    const changed = isMobile !== this.isMobile;
 | 
			
		||||
    this.isMobile = isMobile;
 | 
			
		||||
    this.paginationMaxSize = event.target.innerWidth < 670 ? 3 : 5;
 | 
			
		||||
 | 
			
		||||
    if (changed) {
 | 
			
		||||
      this.changeMode(this.mode);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  changeMode(mode: 'projected' | 'actual'): void {
 | 
			
		||||
    this.router.navigate([], {
 | 
			
		||||
      relativeTo: this.route,
 | 
			
		||||
      queryParams: { showDetails: this.showDetails, view: mode },
 | 
			
		||||
      queryParamsHandling: 'merge',
 | 
			
		||||
      fragment: 'overview'
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onTxClick(event: TransactionStripped): void {
 | 
			
		||||
    const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.txid}`);
 | 
			
		||||
    this.router.navigate([url]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onTxHover(txid: string): void {
 | 
			
		||||
    if (txid && txid.length) {
 | 
			
		||||
      this.hoverTx = txid;
 | 
			
		||||
    } else {
 | 
			
		||||
      this.hoverTx = null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -46,7 +46,7 @@
 | 
			
		||||
            <app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since>
 | 
			
		||||
          </td>
 | 
			
		||||
          <td *ngIf="indexingAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
 | 
			
		||||
            <a class="clear-link" [routerLink]="auditScores[block.id] != null ? ['/block-audit/' | relativeUrl, block.id] : null">
 | 
			
		||||
            <a class="clear-link" [routerLink]="auditScores[block.id] != null ? ['/block/' | relativeUrl, block.id] : null">
 | 
			
		||||
              <div class="progress progress-health">
 | 
			
		||||
                <div class="progress-bar progress-bar-health" role="progressbar"
 | 
			
		||||
                  [ngStyle]="{'width': (100 - (auditScores[block.id] || 0)) + '%' }"></div>
 | 
			
		||||
 | 
			
		||||
@ -141,7 +141,7 @@ export interface TransactionStripped {
 | 
			
		||||
  fee: number;
 | 
			
		||||
  vsize: number;
 | 
			
		||||
  value: number;
 | 
			
		||||
  status?: 'found' | 'missing' | 'added' | 'censored' | 'selected';
 | 
			
		||||
  status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface RewardStats {
 | 
			
		||||
 | 
			
		||||
@ -70,7 +70,8 @@ export interface TransactionStripped {
 | 
			
		||||
  fee: number;
 | 
			
		||||
  vsize: number;
 | 
			
		||||
  value: number;
 | 
			
		||||
  status?: 'found' | 'missing' | 'added' | 'censored' | 'selected';
 | 
			
		||||
  status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
 | 
			
		||||
  context?: 'projected' | 'actual';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IBackendInfo {
 | 
			
		||||
 | 
			
		||||
@ -45,7 +45,6 @@ import { StartComponent } from '../components/start/start.component';
 | 
			
		||||
import { TransactionComponent } from '../components/transaction/transaction.component';
 | 
			
		||||
import { TransactionsListComponent } from '../components/transactions-list/transactions-list.component';
 | 
			
		||||
import { BlockComponent } from '../components/block/block.component';
 | 
			
		||||
import { BlockAuditComponent } from '../components/block-audit/block-audit.component';
 | 
			
		||||
import { BlockOverviewGraphComponent } from '../components/block-overview-graph/block-overview-graph.component';
 | 
			
		||||
import { BlockOverviewTooltipComponent } from '../components/block-overview-tooltip/block-overview-tooltip.component';
 | 
			
		||||
import { AddressComponent } from '../components/address/address.component';
 | 
			
		||||
@ -120,7 +119,6 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
 | 
			
		||||
    StartComponent,
 | 
			
		||||
    TransactionComponent,
 | 
			
		||||
    BlockComponent,
 | 
			
		||||
    BlockAuditComponent,
 | 
			
		||||
    BlockOverviewGraphComponent,
 | 
			
		||||
    BlockOverviewTooltipComponent,
 | 
			
		||||
    TransactionsListComponent,
 | 
			
		||||
@ -223,7 +221,6 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
 | 
			
		||||
    StartComponent,
 | 
			
		||||
    TransactionComponent,
 | 
			
		||||
    BlockComponent,
 | 
			
		||||
    BlockAuditComponent,
 | 
			
		||||
    BlockOverviewGraphComponent,
 | 
			
		||||
    BlockOverviewTooltipComponent,
 | 
			
		||||
    TransactionsListComponent,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user