Merge branch 'master' into simon/mempool-node-group-page
This commit is contained in:
		
						commit
						1b2e7090c3
					
				@ -1,9 +1,16 @@
 | 
				
			|||||||
<a *ngIf="channel; else default" [routerLink]="['/lightning/channel' | relativeUrl, channel.id]">
 | 
					<ng-template [ngIf]="channel" [ngIfElse]="default">
 | 
				
			||||||
  <span
 | 
					  <div>
 | 
				
			||||||
    *ngIf="label"
 | 
					    <div class="badge-positioner">
 | 
				
			||||||
    class="badge badge-pill badge-warning"
 | 
					      <a [routerLink]="['/lightning/channel' | relativeUrl, channel.id]">
 | 
				
			||||||
  >{{ label }}</span>
 | 
					        <span 
 | 
				
			||||||
</a>
 | 
					          *ngIf="label"
 | 
				
			||||||
 | 
					          class="badge badge-pill badge-warning"
 | 
				
			||||||
 | 
					        >{{ label }}</span>
 | 
				
			||||||
 | 
					      </a>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					     
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</ng-template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<ng-template #default>
 | 
					<ng-template #default>
 | 
				
			||||||
  <span
 | 
					  <span
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,7 @@
 | 
				
			|||||||
.badge {
 | 
					.badge {
 | 
				
			||||||
  margin-right: 2px;
 | 
					  margin-right: 2px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.badge-positioner {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										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 'src/app/interfaces/node-api.interface';
 | 
				
			||||||
 | 
					import { ApiService } from 'src/app/services/api.service';
 | 
				
			||||||
 | 
					import { StateService } from 'src/app/services/state.service';
 | 
				
			||||||
 | 
					import { formatNumber } from '@angular/common';
 | 
				
			||||||
 | 
					import { SeoService } from 'src/app/services/seo.service';
 | 
				
			||||||
 | 
					import { OpenGraphService } from 'src/app/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);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -190,6 +190,24 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <br>
 | 
					    <br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="title">
 | 
				
			||||||
 | 
					      <h2 i18n="transaction.diagram|Transaction diagram">Diagram</h2>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="box">
 | 
				
			||||||
 | 
					      <div class="graph-container" #graphContainer>
 | 
				
			||||||
 | 
					        <tx-bowtie-graph [tx]="tx" [width]="graphWidth" [height]="graphExpanded ? (maxInOut * 15) : graphHeight" [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">
 | 
					    <div class="title float-left">
 | 
				
			||||||
      <h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
 | 
					      <h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@ -283,6 +301,36 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <br>
 | 
					    <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">
 | 
					    <div class="title">
 | 
				
			||||||
      <h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
 | 
					      <h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
 | 
				
			||||||
    </div>
 | 
					    </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) {
 | 
					@media (max-width: 767.98px) {
 | 
				
			||||||
	.mobile-bottomcol {
 | 
						.mobile-bottomcol {
 | 
				
			||||||
		margin-top: 15px;
 | 
							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 { ElectrsApiService } from '../../services/electrs-api.service';
 | 
				
			||||||
import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
					import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@ -24,7 +24,7 @@ import { LiquidUnblinding } from './liquid-ublinding';
 | 
				
			|||||||
  templateUrl: './transaction.component.html',
 | 
					  templateUrl: './transaction.component.html',
 | 
				
			||||||
  styleUrls: ['./transaction.component.scss'],
 | 
					  styleUrls: ['./transaction.component.scss'],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class TransactionComponent implements OnInit, OnDestroy {
 | 
					export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			||||||
  network = '';
 | 
					  network = '';
 | 
				
			||||||
  tx: Transaction;
 | 
					  tx: Transaction;
 | 
				
			||||||
  txId: string;
 | 
					  txId: string;
 | 
				
			||||||
@ -47,6 +47,14 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  timeAvg$: Observable<number>;
 | 
					  timeAvg$: Observable<number>;
 | 
				
			||||||
  liquidUnblinding = new LiquidUnblinding();
 | 
					  liquidUnblinding = new LiquidUnblinding();
 | 
				
			||||||
  outputIndex: number;
 | 
					  outputIndex: number;
 | 
				
			||||||
 | 
					  graphExpanded: boolean = false;
 | 
				
			||||||
 | 
					  graphWidth: number = 1000;
 | 
				
			||||||
 | 
					  graphHeight: number = 360;
 | 
				
			||||||
 | 
					  maxInOut: number = 0;
 | 
				
			||||||
 | 
					  tooltipPosition: { x: number, y: number };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @ViewChild('graphContainer')
 | 
				
			||||||
 | 
					  graphContainer: ElementRef;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private route: ActivatedRoute,
 | 
					    private route: ActivatedRoute,
 | 
				
			||||||
@ -167,6 +175,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
          this.waitingForTransaction = false;
 | 
					          this.waitingForTransaction = false;
 | 
				
			||||||
          this.setMempoolBlocksSubscription();
 | 
					          this.setMempoolBlocksSubscription();
 | 
				
			||||||
          this.websocketService.startTrackTransaction(tx.txid);
 | 
					          this.websocketService.startTrackTransaction(tx.txid);
 | 
				
			||||||
 | 
					          this.setupGraph();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (!tx.status.confirmed && tx.firstSeen) {
 | 
					          if (!tx.status.confirmed && tx.firstSeen) {
 | 
				
			||||||
            this.transactionTime = tx.firstSeen;
 | 
					            this.transactionTime = tx.firstSeen;
 | 
				
			||||||
@ -222,6 +231,10 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngAfterViewInit(): void {
 | 
				
			||||||
 | 
					    this.setGraphSize();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleLoadElectrsTransactionError(error: any): Observable<any> {
 | 
					  handleLoadElectrsTransactionError(error: any): Observable<any> {
 | 
				
			||||||
    if (error.status === 404 && /^[a-fA-F0-9]{64}$/.test(this.txId)) {
 | 
					    if (error.status === 404 && /^[a-fA-F0-9]{64}$/.test(this.txId)) {
 | 
				
			||||||
      this.websocketService.startMultiTrackTransaction(this.txId);
 | 
					      this.websocketService.startMultiTrackTransaction(this.txId);
 | 
				
			||||||
@ -284,6 +297,26 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
    return +(cpfpTx.fee / (cpfpTx.weight / 4)).toFixed(1);
 | 
					    return +(cpfpTx.fee / (cpfpTx.weight / 4)).toFixed(1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setupGraph() {
 | 
				
			||||||
 | 
					    this.maxInOut = Math.min(250, 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() {
 | 
					  ngOnDestroy() {
 | 
				
			||||||
    this.subscription.unsubscribe();
 | 
					    this.subscription.unsubscribe();
 | 
				
			||||||
    this.fetchCpfpSubscription.unsubscribe();
 | 
					    this.fetchCpfpSubscription.unsubscribe();
 | 
				
			||||||
 | 
				
			|||||||
@ -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 'src/app/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'">
 | 
					<div class="bowtie-graph">
 | 
				
			||||||
  <defs>
 | 
					  <svg *ngIf="inputs && outputs" class="bowtie" [attr.height]="(height + 10) + 'px'" [attr.width]="width + 'px'">
 | 
				
			||||||
    <marker id="input-arrow" viewBox="-5 -5 10 10"
 | 
					    <defs>
 | 
				
			||||||
        refX="0" refY="0"
 | 
					      <marker id="input-arrow" viewBox="-5 -5 10 10"
 | 
				
			||||||
        markerUnits="strokeWidth"
 | 
					          refX="0" refY="0"
 | 
				
			||||||
        markerWidth="1.5" markerHeight="1"
 | 
					          markerUnits="strokeWidth"
 | 
				
			||||||
        orient="auto">
 | 
					          markerWidth="1.5" markerHeight="1"
 | 
				
			||||||
      <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]"/>
 | 
					          orient="auto">
 | 
				
			||||||
    </marker>
 | 
					        <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 id="output-arrow" viewBox="-5 -5 10 10"
 | 
					      </marker>
 | 
				
			||||||
        refX="0" refY="0"
 | 
					      <marker id="output-arrow" viewBox="-5 -5 10 10"
 | 
				
			||||||
        markerUnits="strokeWidth"
 | 
					          refX="0" refY="0"
 | 
				
			||||||
        markerWidth="1.5" markerHeight="1"
 | 
					          markerUnits="strokeWidth"
 | 
				
			||||||
        orient="auto">
 | 
					          markerWidth="1.5" markerHeight="1"
 | 
				
			||||||
      <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]"/>
 | 
					          orient="auto">
 | 
				
			||||||
    </marker>
 | 
					        <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 id="fee-arrow" viewBox="-5 -5 10 10"
 | 
					      </marker>
 | 
				
			||||||
        refX="0" refY="0"
 | 
					      <marker id="fee-arrow" viewBox="-5 -5 10 10"
 | 
				
			||||||
        markerUnits="strokeWidth"
 | 
					          refX="0" refY="0"
 | 
				
			||||||
        markerWidth="1.5" markerHeight="1"
 | 
					          markerUnits="strokeWidth"
 | 
				
			||||||
        orient="auto">
 | 
					          markerWidth="1.5" markerHeight="1"
 | 
				
			||||||
    </marker>
 | 
					          orient="auto">
 | 
				
			||||||
    <linearGradient id="input-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
					      </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="0%" [attr.stop-color]="gradient[0]" />
 | 
				
			||||||
      <stop offset="100%" [attr.stop-color]="gradient[1]" />
 | 
					      <stop offset="2%" [attr.stop-color]="gradient[0]" />
 | 
				
			||||||
    </linearGradient>
 | 
					        <stop offset="30%" stop-color="white" />
 | 
				
			||||||
    <linearGradient id="output-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
					        <stop offset="100%" [attr.stop-color]="gradient[1]" />
 | 
				
			||||||
      <stop offset="0%" [attr.stop-color]="gradient[1]" />
 | 
					      </linearGradient>
 | 
				
			||||||
      <stop offset="100%" [attr.stop-color]="gradient[0]" />
 | 
					      <linearGradient id="output-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
				
			||||||
    </linearGradient>
 | 
					        <stop offset="0%" [attr.stop-color]="gradient[1]" />
 | 
				
			||||||
    <linearGradient id="fee-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
					        <stop offset="70%" stop-color="white" />
 | 
				
			||||||
      <stop offset="0%" [attr.stop-color]="gradient[1]" />
 | 
					        <stop offset="98%" [attr.stop-color]="gradient[0]" />
 | 
				
			||||||
      <stop offset="50%" [attr.stop-color]="gradient[1]" />
 | 
					        <stop offset="100%" [attr.stop-color]="gradient[0]" />
 | 
				
			||||||
      <stop offset="100%" stop-color="transparent" />
 | 
					      </linearGradient>
 | 
				
			||||||
    </linearGradient>
 | 
					      <linearGradient id="fee-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
				
			||||||
  </defs>
 | 
					        <stop offset="0%" [attr.stop-color]="gradient[1]" />
 | 
				
			||||||
  <path [attr.d]="middle.path" class="line middle" [style]="middle.style"/>
 | 
					        <stop offset="100%" stop-color="white" />
 | 
				
			||||||
  <ng-container *ngFor="let input of inputs">
 | 
					      </linearGradient>
 | 
				
			||||||
    <path [attr.d]="input.path" class="line {{input.class}}" [style]="input.style" attr.marker-start="url(#{{input.class}}-arrow)"/>
 | 
					      <linearGradient id="fee-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
				
			||||||
  </ng-container>
 | 
					        <stop offset="0%" [attr.stop-color]="gradient[1]" />
 | 
				
			||||||
  <ng-container *ngFor="let output of outputs">
 | 
					        <stop offset="50%" [attr.stop-color]="gradient[1]" />
 | 
				
			||||||
    <path [attr.d]="output.path" class="line {{output.class}}" [style]="output.style" attr.marker-start="url(#{{output.class}}-arrow)" />
 | 
					        <stop offset="100%" stop-color="transparent" />
 | 
				
			||||||
  </ng-container>
 | 
					      </linearGradient>
 | 
				
			||||||
</svg>
 | 
					    </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 {
 | 
					    &.fee {
 | 
				
			||||||
      stroke: url(#fee-gradient);
 | 
					      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';
 | 
					import { Transaction } from '../../interfaces/electrs.interface';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface SvgLine {
 | 
					interface SvgLine {
 | 
				
			||||||
@ -7,6 +7,20 @@ interface SvgLine {
 | 
				
			|||||||
  class?: string;
 | 
					  class?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Xput {
 | 
				
			||||||
 | 
					  type: 'input' | 'output' | 'fee';
 | 
				
			||||||
 | 
					  value?: number;
 | 
				
			||||||
 | 
					  index?: number;
 | 
				
			||||||
 | 
					  address?: string;
 | 
				
			||||||
 | 
					  rest?: number;
 | 
				
			||||||
 | 
					  coinbase?: boolean;
 | 
				
			||||||
 | 
					  pegin?: boolean;
 | 
				
			||||||
 | 
					  pegout?: string;
 | 
				
			||||||
 | 
					  confidential?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const lineLimit = 250;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'tx-bowtie-graph',
 | 
					  selector: 'tx-bowtie-graph',
 | 
				
			||||||
  templateUrl: './tx-bowtie-graph.component.html',
 | 
					  templateUrl: './tx-bowtie-graph.component.html',
 | 
				
			||||||
@ -20,11 +34,17 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
 | 
				
			|||||||
  @Input() combinedWeight = 100;
 | 
					  @Input() combinedWeight = 100;
 | 
				
			||||||
  @Input() minWeight = 2; //
 | 
					  @Input() minWeight = 2; //
 | 
				
			||||||
  @Input() maxStrands = 24; // number of inputs/outputs to keep fully on-screen.
 | 
					  @Input() maxStrands = 24; // number of inputs/outputs to keep fully on-screen.
 | 
				
			||||||
 | 
					  @Input() tooltip = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  inputData: Xput[];
 | 
				
			||||||
 | 
					  outputData: Xput[];
 | 
				
			||||||
  inputs: SvgLine[];
 | 
					  inputs: SvgLine[];
 | 
				
			||||||
  outputs: SvgLine[];
 | 
					  outputs: SvgLine[];
 | 
				
			||||||
  middle: SvgLine;
 | 
					  middle: SvgLine;
 | 
				
			||||||
 | 
					  midWidth: number;
 | 
				
			||||||
  isLiquid: boolean = false;
 | 
					  isLiquid: boolean = false;
 | 
				
			||||||
 | 
					  hoverLine: Xput | void = null;
 | 
				
			||||||
 | 
					  tooltipPosition = { x: 0, y: 0 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  gradientColors = {
 | 
					  gradientColors = {
 | 
				
			||||||
    '': ['#9339f4', '#105fb0'],
 | 
					    '': ['#9339f4', '#105fb0'],
 | 
				
			||||||
@ -44,28 +64,68 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
 | 
				
			|||||||
  ngOnInit(): void {
 | 
					  ngOnInit(): void {
 | 
				
			||||||
    this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet');
 | 
					    this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet');
 | 
				
			||||||
    this.gradient = this.gradientColors[this.network];
 | 
					    this.gradient = this.gradientColors[this.network];
 | 
				
			||||||
 | 
					    this.midWidth = Math.min(50, Math.ceil(this.width / 20));
 | 
				
			||||||
    this.initGraph();
 | 
					    this.initGraph();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngOnChanges(): void {
 | 
					  ngOnChanges(): void {
 | 
				
			||||||
    this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet');
 | 
					    this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet');
 | 
				
			||||||
    this.gradient = this.gradientColors[this.network];
 | 
					    this.gradient = this.gradientColors[this.network];
 | 
				
			||||||
 | 
					    this.midWidth = Math.min(50, Math.ceil(this.width / 20));
 | 
				
			||||||
    this.initGraph();
 | 
					    this.initGraph();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  initGraph(): void {
 | 
					  initGraph(): void {
 | 
				
			||||||
    const totalValue = this.calcTotalValue(this.tx);
 | 
					    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) {
 | 
					    if (this.tx.fee && !this.isLiquid) {
 | 
				
			||||||
      voutWithFee.unshift({ type: 'fee', value: this.tx.fee });
 | 
					      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 > lineLimit) {
 | 
				
			||||||
 | 
					      const valueOfRest = truncatedInputs.slice(lineLimit).reduce((r, v) => {
 | 
				
			||||||
 | 
					        return r + (v.value || 0);
 | 
				
			||||||
 | 
					      }, 0);
 | 
				
			||||||
 | 
					      truncatedInputs = truncatedInputs.slice(0, lineLimit);
 | 
				
			||||||
 | 
					      truncatedInputs.push({ type: 'input', value: valueOfRest, rest: this.tx.vin.length - lineLimit });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (voutWithFee.length > lineLimit) {
 | 
				
			||||||
 | 
					      const valueOfRest = voutWithFee.slice(lineLimit).reduce((r, v) => {
 | 
				
			||||||
 | 
					        return r + (v.value || 0);
 | 
				
			||||||
 | 
					      }, 0);
 | 
				
			||||||
 | 
					      voutWithFee = voutWithFee.slice(0, lineLimit);
 | 
				
			||||||
 | 
					      voutWithFee.push({ type: 'output', value: valueOfRest, rest: outputCount - 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.outputs = this.initLines('out', voutWithFee, totalValue, this.maxStrands);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.middle = {
 | 
					    this.middle = {
 | 
				
			||||||
      path: `M ${(this.width / 2) - 50} ${(this.height / 2) + 0.5} L ${(this.width / 2) + 50} ${(this.height / 2) + 0.5}`,
 | 
					      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 + 0.5}; stroke: ${this.gradient[1]}`
 | 
					      style: `stroke-width: ${this.combinedWeight + 0.5}; stroke: ${this.gradient[1]}`
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -95,7 +155,7 @@ 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) {
 | 
					    if (!total) {
 | 
				
			||||||
      const weights = xputs.map((put): number => this.combinedWeight / xputs.length);
 | 
					      const weights = xputs.map((put): number => this.combinedWeight / xputs.length);
 | 
				
			||||||
      return this.linesFromWeights(side, xputs, weights, maxVisibleStrands);
 | 
					      return this.linesFromWeights(side, xputs, weights, maxVisibleStrands);
 | 
				
			||||||
@ -116,7 +176,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  linesFromWeights(side: 'in' | 'out', xputs: { type: string, value: number | void }[], weights: number[], maxVisibleStrands: number) {
 | 
					  linesFromWeights(side: 'in' | 'out', xputs: Xput[], weights: number[], maxVisibleStrands: number) {
 | 
				
			||||||
    const lines = [];
 | 
					    const lines = [];
 | 
				
			||||||
    // actual displayed line thicknesses
 | 
					    // actual displayed line thicknesses
 | 
				
			||||||
    const minWeights = weights.map((w) => Math.max(this.minWeight - 1, w) + 1);
 | 
					    const minWeights = weights.map((w) => Math.max(this.minWeight - 1, w) + 1);
 | 
				
			||||||
@ -158,7 +218,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  makePath(side: 'in' | 'out', outer: number, inner: number, weight: number): string {
 | 
					  makePath(side: 'in' | 'out', outer: number, inner: number, weight: number): string {
 | 
				
			||||||
    const start = side === 'in' ? (weight * 0.5) : this.width - (weight * 0.5);
 | 
					    const start = side === 'in' ? (weight * 0.5) : this.width - (weight * 0.5);
 | 
				
			||||||
    const center =  this.width / 2 + (side === 'in' ? -45 : 45 );
 | 
					    const center =  this.width / 2 + (side === 'in' ? -(this.midWidth * 0.9) : (this.midWidth * 0.9) );
 | 
				
			||||||
    const midpoint = (start + center) / 2;
 | 
					    const midpoint = (start + center) / 2;
 | 
				
			||||||
    // correct for svg horizontal gradient bug
 | 
					    // correct for svg horizontal gradient bug
 | 
				
			||||||
    if (Math.round(outer) === Math.round(inner)) {
 | 
					    if (Math.round(outer) === Math.round(inner)) {
 | 
				
			||||||
@ -169,9 +229,32 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  makeStyle(minWeight, type): string {
 | 
					  makeStyle(minWeight, type): string {
 | 
				
			||||||
    if (type === 'fee') {
 | 
					    if (type === 'fee') {
 | 
				
			||||||
      return `stroke-width: ${minWeight}; stroke: url(#fee-gradient)`;
 | 
					      return `stroke-width: ${minWeight}`;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return `stroke-width: ${minWeight}`;
 | 
					      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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -7,20 +7,22 @@ import { PreviewsRoutingModule } from './previews.routing.module';
 | 
				
			|||||||
import { TransactionPreviewComponent } from './components/transaction/transaction-preview.component';
 | 
					import { TransactionPreviewComponent } from './components/transaction/transaction-preview.component';
 | 
				
			||||||
import { BlockPreviewComponent } from './components/block/block-preview.component';
 | 
					import { BlockPreviewComponent } from './components/block/block-preview.component';
 | 
				
			||||||
import { AddressPreviewComponent } from './components/address/address-preview.component';
 | 
					import { AddressPreviewComponent } from './components/address/address-preview.component';
 | 
				
			||||||
 | 
					import { PoolPreviewComponent } from './components/pool/pool-preview.component';
 | 
				
			||||||
import { MasterPagePreviewComponent } from './components/master-page-preview/master-page-preview.component';
 | 
					import { MasterPagePreviewComponent } from './components/master-page-preview/master-page-preview.component';
 | 
				
			||||||
@NgModule({
 | 
					@NgModule({
 | 
				
			||||||
  declarations: [
 | 
					  declarations: [
 | 
				
			||||||
    TransactionPreviewComponent,
 | 
					    TransactionPreviewComponent,
 | 
				
			||||||
    BlockPreviewComponent,
 | 
					    BlockPreviewComponent,
 | 
				
			||||||
    AddressPreviewComponent,
 | 
					    AddressPreviewComponent,
 | 
				
			||||||
 | 
					    PoolPreviewComponent,
 | 
				
			||||||
    MasterPagePreviewComponent,
 | 
					    MasterPagePreviewComponent,
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  imports: [
 | 
					  imports: [
 | 
				
			||||||
    CommonModule,
 | 
					    CommonModule,
 | 
				
			||||||
    SharedModule,
 | 
					    SharedModule,
 | 
				
			||||||
    RouterModule,
 | 
					    RouterModule,
 | 
				
			||||||
    GraphsModule,
 | 
					 | 
				
			||||||
    PreviewsRoutingModule,
 | 
					    PreviewsRoutingModule,
 | 
				
			||||||
 | 
					    GraphsModule,
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class PreviewsModule { }
 | 
					export class PreviewsModule { }
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
 | 
				
			|||||||
import { TransactionPreviewComponent } from './components/transaction/transaction-preview.component';
 | 
					import { TransactionPreviewComponent } from './components/transaction/transaction-preview.component';
 | 
				
			||||||
import { BlockPreviewComponent } from './components/block/block-preview.component';
 | 
					import { BlockPreviewComponent } from './components/block/block-preview.component';
 | 
				
			||||||
import { AddressPreviewComponent } from './components/address/address-preview.component';
 | 
					import { AddressPreviewComponent } from './components/address/address-preview.component';
 | 
				
			||||||
 | 
					import { PoolPreviewComponent } from './components/pool/pool-preview.component';
 | 
				
			||||||
import { MasterPagePreviewComponent } from './components/master-page-preview/master-page-preview.component';
 | 
					import { MasterPagePreviewComponent } from './components/master-page-preview/master-page-preview.component';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const routes: Routes = [
 | 
					const routes: Routes = [
 | 
				
			||||||
@ -24,6 +25,10 @@ const routes: Routes = [
 | 
				
			|||||||
        children: [],
 | 
					        children: [],
 | 
				
			||||||
        component: TransactionPreviewComponent
 | 
					        component: TransactionPreviewComponent
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        path: 'mining/pool/:slug',
 | 
				
			||||||
 | 
					        component: PoolPreviewComponent
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        path: 'lightning',
 | 
					        path: 'lightning',
 | 
				
			||||||
        loadChildren: () => import('./lightning/lightning-previews.module').then(m => m.LightningPreviewsModule)
 | 
					        loadChildren: () => import('./lightning/lightning-previews.module').then(m => m.LightningPreviewsModule)
 | 
				
			||||||
 | 
				
			|||||||
@ -61,6 +61,7 @@ import { FeesBoxComponent } from '../components/fees-box/fees-box.component';
 | 
				
			|||||||
import { DifficultyComponent } from '../components/difficulty/difficulty.component';
 | 
					import { DifficultyComponent } from '../components/difficulty/difficulty.component';
 | 
				
			||||||
import { TermsOfServiceComponent } from '../components/terms-of-service/terms-of-service.component';
 | 
					import { TermsOfServiceComponent } from '../components/terms-of-service/terms-of-service.component';
 | 
				
			||||||
import { TxBowtieGraphComponent } from '../components/tx-bowtie-graph/tx-bowtie-graph.component';
 | 
					import { TxBowtieGraphComponent } from '../components/tx-bowtie-graph/tx-bowtie-graph.component';
 | 
				
			||||||
 | 
					import { TxBowtieGraphTooltipComponent } from '../components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component';
 | 
				
			||||||
import { PrivacyPolicyComponent } from '../components/privacy-policy/privacy-policy.component';
 | 
					import { PrivacyPolicyComponent } from '../components/privacy-policy/privacy-policy.component';
 | 
				
			||||||
import { TrademarkPolicyComponent } from '../components/trademark-policy/trademark-policy.component';
 | 
					import { TrademarkPolicyComponent } from '../components/trademark-policy/trademark-policy.component';
 | 
				
			||||||
import { PushTransactionComponent } from '../components/push-transaction/push-transaction.component';
 | 
					import { PushTransactionComponent } from '../components/push-transaction/push-transaction.component';
 | 
				
			||||||
@ -134,6 +135,7 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
 | 
				
			|||||||
    FeesBoxComponent,
 | 
					    FeesBoxComponent,
 | 
				
			||||||
    DifficultyComponent,
 | 
					    DifficultyComponent,
 | 
				
			||||||
    TxBowtieGraphComponent,
 | 
					    TxBowtieGraphComponent,
 | 
				
			||||||
 | 
					    TxBowtieGraphTooltipComponent,
 | 
				
			||||||
    TermsOfServiceComponent,
 | 
					    TermsOfServiceComponent,
 | 
				
			||||||
    PrivacyPolicyComponent,
 | 
					    PrivacyPolicyComponent,
 | 
				
			||||||
    TrademarkPolicyComponent,
 | 
					    TrademarkPolicyComponent,
 | 
				
			||||||
@ -236,6 +238,7 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
 | 
				
			|||||||
    FeesBoxComponent,
 | 
					    FeesBoxComponent,
 | 
				
			||||||
    DifficultyComponent,
 | 
					    DifficultyComponent,
 | 
				
			||||||
    TxBowtieGraphComponent,
 | 
					    TxBowtieGraphComponent,
 | 
				
			||||||
 | 
					    TxBowtieGraphTooltipComponent,
 | 
				
			||||||
    TermsOfServiceComponent,
 | 
					    TermsOfServiceComponent,
 | 
				
			||||||
    PrivacyPolicyComponent,
 | 
					    PrivacyPolicyComponent,
 | 
				
			||||||
    TrademarkPolicyComponent,
 | 
					    TrademarkPolicyComponent,
 | 
				
			||||||
 | 
				
			|||||||
@ -61,7 +61,16 @@ const routes = {
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  mining: {
 | 
					  mining: {
 | 
				
			||||||
    title: "Mining",
 | 
					    title: "Mining",
 | 
				
			||||||
    fallbackImg: '/resources/previews/mining.png'
 | 
					    fallbackImg: '/resources/previews/mining.png',
 | 
				
			||||||
 | 
					    routes: {
 | 
				
			||||||
 | 
					      pool: {
 | 
				
			||||||
 | 
					        render: true,
 | 
				
			||||||
 | 
					        params: 1,
 | 
				
			||||||
 | 
					        getTitle(path) {
 | 
				
			||||||
 | 
					          return `Mining Pool: ${path[0]}`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user