Merge pull request #4321 from mempool/mononaut/ssr

Angular Universal SSR
This commit is contained in:
softsimon
2024-03-22 20:01:30 +09:00
committed by GitHub
94 changed files with 3390 additions and 300 deletions

View File

@@ -45,10 +45,10 @@
</form>
</div>
<div [class.chart]="!widget" [class.chart-widget]="widget" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
<div [class.chart]="!widget" [class.chart-widget]="widget" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnDestroy, OnInit } from '@angular/core';
import { EChartsOption } from 'echarts';
import { EChartsOption } from '../../../graphs/echarts';
import { Observable, Subscription, combineLatest, fromEvent, share } from 'rxjs';
import { startWith, switchMap, tap } from 'rxjs/operators';
import { SeoService } from '../../../services/seo.service';
@@ -11,6 +11,7 @@ import { MiningService } from '../../../services/mining.service';
import { ActivatedRoute } from '@angular/router';
import { Acceleration } from '../../../interfaces/node-api.interface';
import { ServicesApiServices } from '../../../services/services-api.service';
import { StateService } from '../../../services/state.service';
@Component({
selector: 'app-acceleration-fees-graph',
@@ -59,6 +60,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
private storageService: StorageService,
private miningService: MiningService,
private route: ActivatedRoute,
public stateService: StateService,
private cd: ChangeDetectorRef,
) {
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
@@ -176,10 +178,10 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
padding: [10, 0, 0, 0],
},
type: 'time',
boundaryGap: false,
boundaryGap: [0, 0],
axisLine: { onZero: true },
axisLabel: {
formatter: val => formatterXAxisTimeCategory(this.locale, this.timespan, parseInt(val, 10)),
formatter: (val): string => formatterXAxisTimeCategory(this.locale, this.timespan, val),
align: 'center',
fontSize: 11,
lineHeight: 12,

View File

@@ -42,7 +42,7 @@
<span>&nbsp;</span>
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: #4a68b9"></fa-icon>
</a>
<div class="mempool-block-wrapper">
<div class="mempool-block-wrapper" *ngIf="webGlEnabled">
<app-mempool-block-overview [index]="0" [overrideColors]="getAcceleratorColor"></app-mempool-block-overview>
</div>
</div>
@@ -66,16 +66,6 @@
</div>
</div>
<!-- Avg block fees graph -->
<!-- <div class="col" style="margin-bottom: 1.47rem">
<div class="card">
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2">
<app-block-fee-rates-graph [attr.data-cy]="'hashrate-graph'" [widget]="true"></app-block-fee-rates-graph>
<div class="mt-1"><a [routerLink]="['/graphs/mining/block-fee-rates' | relativeUrl]" fragment="1m" i18n="dashboard.view-more">View more &raquo;</a></div>
</div>
</div>
</div> -->
<!-- Recent Accelerations List -->
<div class="col">
<div class="card list-card">

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, HostListener, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, HostListener, Inject, OnInit, PLATFORM_ID } from '@angular/core';
import { SeoService } from '../../../services/seo.service';
import { OpenGraphService } from '../../../services/opengraph.service';
import { WebsocketService } from '../../../services/websocket.service';
@@ -10,6 +10,7 @@ import { hexToColor } from '../../block-overview-graph/utils';
import TxView from '../../block-overview-graph/tx-view';
import { feeLevels, mempoolFeeColors } from '../../../app.constants';
import { ServicesApiServices } from '../../../services/services-api.service';
import { detectWebGL } from '../../../shared/graphs.utils';
const acceleratedColor: Color = hexToColor('8F5FF6');
const normalColors = mempoolFeeColors.map(hex => hexToColor(hex + '5F'));
@@ -30,6 +31,7 @@ export class AcceleratorDashboardComponent implements OnInit {
pendingAccelerations$: Observable<Acceleration[]>;
minedAccelerations$: Observable<Acceleration[]>;
loadingBlocks: boolean = true;
webGlEnabled = true;
graphHeight: number = 300;
@@ -39,7 +41,9 @@ export class AcceleratorDashboardComponent implements OnInit {
private websocketService: WebsocketService,
private serviceApiServices: ServicesApiServices,
private stateService: StateService,
@Inject(PLATFORM_ID) private platformId: Object,
) {
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
this.seoService.setTitle($localize`:@@a681a4e2011bb28157689dbaa387de0dd0aa0c11:Accelerator Dashboard`);
this.ogService.setManualOgImage('accelerator.jpg');
}
@@ -48,7 +52,7 @@ export class AcceleratorDashboardComponent implements OnInit {
this.onResize();
this.websocketService.want(['blocks', 'mempool-blocks', 'stats']);
this.pendingAccelerations$ = interval(30000).pipe(
this.pendingAccelerations$ = (this.stateService.isBrowser ? interval(30000) : of(null)).pipe(
startWith(true),
switchMap(() => {
return this.serviceApiServices.getAccelerations$().pipe(

View File

@@ -8,7 +8,7 @@
</div>
<ng-container *ngIf="!error">
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">

View File

@@ -62,10 +62,10 @@
</div>
</div>
<div [class.chart]="!widget" [class.chart-widget]="widget" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
<div [class.chart]="!widget" [class.chart-widget]="widget" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, NgZone, OnInit } from '@angular/core';
import { EChartsOption, graphic } from 'echarts';
import { echarts, EChartsOption } from '../../graphs/echarts';
import { Observable, combineLatest, of } from 'rxjs';
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
import { ApiService } from '../../services/api.service';
@@ -55,7 +55,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
private formBuilder: UntypedFormBuilder,
private storageService: StorageService,
private miningService: MiningService,
private stateService: StateService,
public stateService: StateService,
private router: Router,
private zone: NgZone,
private route: ActivatedRoute,
@@ -209,7 +209,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
prepareChartOptions(data, weightMode) {
this.chartOptions = {
color: this.widget ? ['#6b6b6b', new graphic.LinearGradient(0, 0, 0, 0.65, [
color: this.widget ? ['#6b6b6b', new echarts.graphic.LinearGradient(0, 0, 0, 0.65, [
{ offset: 0, color: '#F4511E' },
{ offset: 0.25, color: '#FB8C00' },
{ offset: 0.5, color: '#FFB300' },
@@ -282,7 +282,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
legend: (this.widget || data.series.length === 0) ? undefined : {
padding: [10, 75],
data: data.legends,
selected: JSON.parse(this.storageService.getValue('fee_rates_legend')) ?? {
selected: JSON.parse(this.storageService.getValue('fee_rates_legend') || 'null') ?? {
'Min': true,
'10th': true,
'25th': true,

View File

@@ -36,10 +36,10 @@
</form>
</div>
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -12,6 +12,7 @@ import { MiningService } from '../../services/mining.service';
import { ActivatedRoute } from '@angular/router';
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
import { FiatCurrencyPipe } from '../../shared/pipes/fiat-currency.pipe';
import { StateService } from '../../services/state.service';
@Component({
selector: 'app-block-fees-graph',
@@ -54,6 +55,7 @@ export class BlockFeesGraphComponent implements OnInit {
private formBuilder: UntypedFormBuilder,
private storageService: StorageService,
private miningService: MiningService,
public stateService: StateService,
private route: ActivatedRoute,
private fiatShortenerPipe: FiatShortenerPipe,
private fiatCurrencyPipe: FiatCurrencyPipe,

View File

@@ -45,10 +45,10 @@
</form>
</div>
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -52,7 +52,7 @@ export class BlockHealthGraphComponent implements OnInit {
private storageService: StorageService,
private zone: NgZone,
private route: ActivatedRoute,
private stateService: StateService,
public stateService: StateService,
private router: Router,
) {
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });

View File

@@ -1,7 +1,7 @@
<div class="grid-align" [style.gridTemplateColumns]="'repeat(auto-fit, ' + resolution + 'px)'">
<div class="block-overview-graph">
<canvas class="block-overview-canvas" [class.clickable]="!!hoverTx" #blockCanvas></canvas>
<canvas *browserOnly class="block-overview-canvas" [class.clickable]="!!hoverTx" #blockCanvas></canvas>
<div class="loader-wrapper" [class.hidden]="(!isLoading || disableSpinner) && !unavailable">
<div *ngIf="!unavailable" class="spinner-border ml-3 loading" role="status"></div>
<div *ngIf="!isLoading && unavailable" class="ml-3" i18n="block.not-available">not available</div>

View File

@@ -83,9 +83,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
constructor(
readonly ngZone: NgZone,
readonly elRef: ElementRef,
private stateService: StateService,
public stateService: StateService,
) {
this.webGlEnabled = detectWebGL();
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize);
this.searchSubscription = this.stateService.searchText$.subscribe((text) => {
this.searchText = text;
@@ -94,13 +94,15 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
}
ngAfterViewInit(): void {
this.canvas.nativeElement.addEventListener('webglcontextlost', this.handleContextLost, false);
this.canvas.nativeElement.addEventListener('webglcontextrestored', this.handleContextRestored, false);
this.gl = this.canvas.nativeElement.getContext('webgl');
if (this.canvas) {
this.canvas.nativeElement.addEventListener('webglcontextlost', this.handleContextLost, false);
this.canvas.nativeElement.addEventListener('webglcontextrestored', this.handleContextRestored, false);
this.gl = this.canvas.nativeElement.getContext('webgl');
if (this.gl) {
this.initCanvas();
this.resizeCanvas();
if (this.gl) {
this.initCanvas();
this.resizeCanvas();
}
}
}
@@ -142,8 +144,10 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
cancelAnimationFrame(this.animationFrameRequest);
clearTimeout(this.animationHeartBeat);
}
this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost);
this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
if (this.canvas) {
this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost);
this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
}
}
clear(direction): void {
@@ -209,6 +213,10 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
}
initCanvas(): void {
if (!this.canvas || !this.gl) {
return;
}
this.gl.clearColor(0.0, 0.0, 0.0, 0.0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
@@ -262,24 +270,26 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
@HostListener('window:resize', ['$event'])
resizeCanvas(): void {
this.cssWidth = this.canvas.nativeElement.offsetParent.clientWidth;
this.cssHeight = this.canvas.nativeElement.offsetParent.clientHeight;
this.displayWidth = window.devicePixelRatio * this.cssWidth;
this.displayHeight = window.devicePixelRatio * this.cssHeight;
this.canvas.nativeElement.width = this.displayWidth;
this.canvas.nativeElement.height = this.displayHeight;
if (this.gl) {
this.gl.viewport(0, 0, this.displayWidth, this.displayHeight);
}
if (this.scene) {
this.scene.resize({ width: this.displayWidth, height: this.displayHeight, animate: false });
this.start();
} else {
this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution,
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray,
highlighting: this.auditHighlighting, animationDuration: this.animationDuration, animationOffset: this.animationOffset,
if (this.canvas) {
this.cssWidth = this.canvas.nativeElement.offsetParent.clientWidth;
this.cssHeight = this.canvas.nativeElement.offsetParent.clientHeight;
this.displayWidth = window.devicePixelRatio * this.cssWidth;
this.displayHeight = window.devicePixelRatio * this.cssHeight;
this.canvas.nativeElement.width = this.displayWidth;
this.canvas.nativeElement.height = this.displayHeight;
if (this.gl) {
this.gl.viewport(0, 0, this.displayWidth, this.displayHeight);
}
if (this.scene) {
this.scene.resize({ width: this.displayWidth, height: this.displayHeight, animate: false });
this.start();
} else {
this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution,
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray,
highlighting: this.auditHighlighting, animationDuration: this.animationDuration, animationOffset: this.animationOffset,
colorFunction: this.getColorFunction() });
this.start();
this.start();
}
}
}
@@ -406,6 +416,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
@HostListener('pointerup', ['$event'])
onClick(event) {
if (!this.canvas) {
return;
}
if (event.target === this.canvas.nativeElement && event.pointerType === 'touch') {
this.setPreviewTx(event.offsetX, event.offsetY, true);
} else if (event.target === this.canvas.nativeElement) {
@@ -417,6 +430,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
@HostListener('pointermove', ['$event'])
onPointerMove(event) {
if (!this.canvas) {
return;
}
if (event.target === this.canvas.nativeElement) {
this.setPreviewTx(event.offsetX, event.offsetY, false);
} else {

View File

@@ -37,10 +37,10 @@
</form>
</div>
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -12,6 +12,7 @@ import { StorageService } from '../../services/storage.service';
import { ActivatedRoute } from '@angular/router';
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
import { FiatCurrencyPipe } from '../../shared/pipes/fiat-currency.pipe';
import { StateService } from '../../services/state.service';
@Component({
selector: 'app-block-rewards-graph',
@@ -54,6 +55,7 @@ export class BlockRewardsGraphComponent implements OnInit {
private formBuilder: UntypedFormBuilder,
private miningService: MiningService,
private storageService: StorageService,
public stateService: StateService,
private route: ActivatedRoute,
private fiatShortenerPipe: FiatShortenerPipe,
private fiatCurrencyPipe: FiatCurrencyPipe,

View File

@@ -44,10 +44,10 @@
</form>
</div>
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -10,6 +10,7 @@ import { StorageService } from '../../services/storage.service';
import { MiningService } from '../../services/mining.service';
import { ActivatedRoute } from '@angular/router';
import { download, formatterXAxis } from '../../shared/graphs.utils';
import { StateService } from '../../services/state.service';
@Component({
selector: 'app-block-sizes-weights-graph',
@@ -52,6 +53,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
private formBuilder: UntypedFormBuilder,
private storageService: StorageService,
private miningService: MiningService,
public stateService: StateService,
private route: ActivatedRoute,
) {
}

View File

@@ -109,7 +109,7 @@
<div class="col-sm chart-container" *ngIf="webGlEnabled && !showAudit">
<app-block-overview-graph
#blockGraphActual
[isLoading]="isLoadingOverview"
[isLoading]="!stateService.isBrowser || isLoadingOverview"
[resolution]="86"
[blockLimit]="stateService.blockVSize"
[orientation]="'top'"
@@ -229,7 +229,7 @@
<div class="col-sm audit-col" [class.mobile]="isMobile">
<h3 class="block-subtitle" *ngIf="!isMobile"><ng-container i18n="block.expected-block">Expected Block</ng-container></h3>
<div class="block-graph-wrapper">
<app-block-overview-graph #blockGraphProjected [isLoading]="isLoadingOverview" [resolution]="86"
<app-block-overview-graph #blockGraphProjected [isLoading]="!stateService.isBrowser || isLoadingOverview" [resolution]="86"
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" [auditHighlighting]="showAudit"
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="!isMobile && !showAudit"
[showFilters]="true" [excludeFilters]="['replacement']"></app-block-overview-graph>
@@ -244,7 +244,7 @@
<div class="col-sm audit-col" *ngIf="!isMobile">
<h3 class="block-subtitle actual" *ngIf="!isMobile"><ng-container i18n="block.actual-block">Actual Block</ng-container><a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="how-do-block-audits-work"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></h3>
<div class="block-graph-wrapper">
<app-block-overview-graph #blockGraphActual [isLoading]="isLoadingOverview" [resolution]="86"
<app-block-overview-graph #blockGraphActual [isLoading]="!stateService.isBrowser || isLoadingOverview" [resolution]="86"
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined" [auditHighlighting]="showAudit"
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="isMobile && !showAudit"
[showFilters]="true" [excludeFilters]="['replacement']"></app-block-overview-graph>

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ViewChildren, QueryList } from '@angular/core';
import { Component, OnInit, OnDestroy, ViewChildren, QueryList, Inject, PLATFORM_ID, ChangeDetectorRef } from '@angular/core';
import { Location } from '@angular/common';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { ElectrsApiService } from '../../services/electrs-api.service';
@@ -108,8 +108,10 @@ export class BlockComponent implements OnInit, OnDestroy {
private priceService: PriceService,
private cacheService: CacheService,
private servicesApiService: ServicesApiServices,
private cd: ChangeDetectorRef,
@Inject(PLATFORM_ID) private platformId: Object,
) {
this.webGlEnabled = detectWebGL();
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
}
ngOnInit() {
@@ -318,6 +320,7 @@ export class BlockComponent implements OnInit, OnDestroy {
}
this.transactions = transactions;
this.isLoadingTransactions = false;
this.cd.markForCheck();
},
(error) => {
this.error = error;
@@ -471,6 +474,7 @@ export class BlockComponent implements OnInit, OnDestroy {
this.isLoadingOverview = false;
this.setupBlockGraphs();
this.cd.markForCheck();
});
this.oobSubscription = block$.pipe(

View File

@@ -33,7 +33,7 @@
<div *ngIf="indexingAvailable" class="tooltip-custom">
<a class="clear-link" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]">
<img width="22" height="22" src="{{ block.extras.pool['logo'] }}"
onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
onError="this.onerror=null; this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
<span class="pool-name">{{ block.extras.pool.name }}</span>
</a>
<span *ngIf="!widget" class="tooltiptext badge badge-secondary scriptmessage">{{ block.extras.coinbaseRaw | hex2ascii }}</span>

View File

@@ -1,6 +1,5 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
import { Subscription, tap, timer } from 'rxjs';
import { WebsocketService } from '../../services/websocket.service';
import { StateService } from '../../services/state.service';
@Component({
@@ -33,17 +32,20 @@ export class ClockFaceComponent implements OnInit, OnChanges, OnDestroy {
}
ngOnInit(): void {
this.timeSubscription = timer(0, 250).pipe(
tap(() => {
this.updateTime();
})
).subscribe();
this.blocksSubscription = this.stateService.blocks$
.subscribe((blocks) => {
this.blockTimes = blocks.map(block => [block.height, new Date(block.timestamp * 1000)]);
this.blockTimes = this.blockTimes.sort((a, b) => a[1].getTime() - b[1].getTime());
this.updateSegments();
});
if (this.stateService.isBrowser) {
this.timeSubscription = timer(0, 250).pipe(
tap(() => {
console.log('face tick');
this.updateTime();
})
).subscribe();
this.blocksSubscription = this.stateService.blocks$
.subscribe((blocks) => {
this.blockTimes = blocks.map(block => [block.height, new Date(block.timestamp * 1000)]);
this.blockTimes = this.blockTimes.sort((a, b) => a[1].getTime() - b[1].getTime());
this.updateSegments();
});
}
}
ngOnChanges(): void {
@@ -54,7 +56,9 @@ export class ClockFaceComponent implements OnInit, OnChanges, OnDestroy {
}
ngOnDestroy(): void {
this.timeSubscription.unsubscribe();
if (this.timeSubscription) {
this.timeSubscription.unsubscribe();
}
}
updateTime(): void {

View File

@@ -110,8 +110,8 @@ export class ClockComponent implements OnInit {
@HostListener('window:resize', ['$event'])
resizeCanvas(): void {
const windowWidth = this.limitWidth || window.innerWidth;
const windowHeight = this.limitHeight || window.innerHeight;
const windowWidth = this.limitWidth || window.innerWidth || 800;
const windowHeight = this.limitHeight || window.innerHeight || 800;
this.chainWidth = windowWidth;
this.chainHeight = Math.max(60, windowHeight / 8);
this.clockSize = Math.min(800, windowWidth, windowHeight - (1.4 * this.chainHeight));

View File

@@ -95,7 +95,7 @@ export class EightBlocksComponent implements OnInit, OnDestroy {
private apiService: ApiService,
private bytesPipe: BytesPipe,
) {
this.webGlEnabled = detectWebGL();
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
}
ngOnInit(): void {

View File

@@ -1,5 +1,5 @@
<div class="fee-distribution-chart" *ngIf="mempoolVsizeFeesOptions; else loadingFees">
<div echarts [initOpts]="mempoolVsizeFeesInitOptions" [options]="mempoolVsizeFeesOptions"></div>
<div class="fee-distribution-chart" *ngIf="mempoolVsizeFeesOptions && stateService.isBrowser; else loadingFees">
<div *browserOnly echarts [initOpts]="mempoolVsizeFeesInitOptions" [options]="mempoolVsizeFeesOptions"></div>
</div>
<ng-template #loadingFees>

View File

@@ -36,7 +36,7 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges, OnDestr
};
constructor(
private stateService: StateService,
public stateService: StateService,
private vbytesPipe: VbytesPipe,
) { }

View File

@@ -54,10 +54,10 @@
</form>
</div>
<div [class]="!widget ? 'chart' : 'chart-widget'" [style]="{ height: widget ? ((height + 20) + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
<div [class]="!widget ? 'chart' : 'chart-widget'" *browserOnly [style]="{ height: widget ? ((height + 20) + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -60,7 +60,7 @@ export class HashrateChartComponent implements OnInit {
private storageService: StorageService,
private miningService: MiningService,
private route: ActivatedRoute,
private stateService: StateService
public stateService: StateService
) {
}
@@ -326,7 +326,7 @@ export class HashrateChartComponent implements OnInit {
},
},
],
selected: JSON.parse(this.storageService.getValue('hashrate_difficulty_legend')) ?? {
selected: JSON.parse(this.storageService?.getValue('hashrate_difficulty_legend') || 'null') ?? {
'$localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`': true,
'$localize`::Difficulty`': this.network === '',
'$localize`Hashrate (MA)`': true,

View File

@@ -31,10 +31,10 @@
</form>
</div>
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -10,6 +10,7 @@ import { StorageService } from '../../services/storage.service';
import { MiningService } from '../../services/mining.service';
import { download } from '../../shared/graphs.utils';
import { ActivatedRoute } from '@angular/router';
import { StateService } from '../../services/state.service';
interface Hashrate {
timestamp: number;
@@ -60,6 +61,7 @@ export class HashrateChartPoolsComponent implements OnInit {
private cd: ChangeDetectorRef,
private storageService: StorageService,
private miningService: MiningService,
public stateService: StateService,
private route: ActivatedRoute,
) {
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });

View File

@@ -1,6 +1,6 @@
<div class="echarts" echarts [initOpts]="mempoolStatsChartInitOption" [options]="mempoolStatsChartOption" (chartRendered)="rendered()"
<div class="echarts" *browserOnly echarts [initOpts]="mempoolStatsChartInitOption" [options]="mempoolStatsChartOption" (chartRendered)="rendered()"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -48,7 +48,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
constructor(
@Inject(LOCALE_ID) private locale: string,
private storageService: StorageService,
private stateService: StateService,
public stateService: StateService,
) { }
ngOnInit() {

View File

@@ -1,4 +1,4 @@
<div class="echarts" echarts [initOpts]="pegsChartInitOption" [options]="pegsChartOptions" (chartRendered)="rendered()"></div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="echarts" *browserOnly echarts [initOpts]="pegsChartInitOption" [options]="pegsChartOptions" (chartRendered)="rendered()"></div>
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -1,6 +1,7 @@
import { Component, Inject, LOCALE_ID, ChangeDetectionStrategy, Input, OnChanges, OnInit } from '@angular/core';
import { formatDate, formatNumber } from '@angular/common';
import { EChartsOption } from '../../graphs/echarts';
import { StateService } from '../../services/state.service';
@Component({
selector: 'app-lbtc-pegs-graph',
@@ -32,6 +33,7 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
};
constructor(
public stateService: StateService,
@Inject(LOCALE_ID) private locale: string,
) { }

View File

@@ -1,4 +1,4 @@
<div class="echarts" echarts [initOpts]="ratioChartInitOptions" [options]="ratioChartOptions" (chartRendered)="rendered()"></div>
<div class="echarts" *browserOnly echarts [initOpts]="ratioChartInitOptions" [options]="ratioChartOptions" (chartRendered)="rendered()"></div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -1,6 +1,6 @@
<app-block-overview-graph
#blockGraph
[isLoading]="isLoading$ | async"
[isLoading]="(isLoading$ | async) || !stateService.isBrowser"
[resolution]="resolution"
[blockLimit]="stateService.blockVSize"
[orientation]="timeLtr ? 'right' : 'left'"

View File

@@ -1,4 +1,5 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Inject, PLATFORM_ID, ChangeDetectorRef } from '@angular/core';
import { detectWebGL } from '../../shared/graphs.utils';
import { StateService } from '../../services/state.service';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { switchMap, map, tap, filter } from 'rxjs/operators';
@@ -29,8 +30,9 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
private seoService: SeoService,
private websocketService: WebsocketService,
private cd: ChangeDetectorRef,
@Inject(PLATFORM_ID) private platformId: Object,
) {
this.webGlEnabled = detectWebGL();
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
}
ngOnInit(): void {
@@ -93,9 +95,3 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
this.previewTx = event;
}
}
function detectWebGL() {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
return (gl && gl instanceof WebGLRenderingContext);
}

View File

@@ -1,4 +1,4 @@
<div echarts class="echarts" (chartInit)="onChartReady($event)" (chartRendered)="rendered()" [initOpts]="mempoolVsizeFeesInitOptions" [options]="mempoolVsizeFeesOptions"></div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div *browserOnly echarts class="echarts" (chartInit)="onChartReady($event)" (chartRendered)="rendered()" [initOpts]="mempoolVsizeFeesInitOptions" [options]="mempoolVsizeFeesOptions"></div>
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -59,7 +59,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
private vbytesPipe: VbytesPipe,
private wubytesPipe: WuBytesPipe,
private amountShortenerPipe: AmountShortenerPipe,
private stateService: StateService,
public stateService: StateService,
private storageService: StorageService,
@Inject(LOCALE_ID) private locale: string,
) { }

View File

@@ -76,11 +76,11 @@
</div>
<div [class]="!widget ? '' : 'pb-0'" class="container pb-lg-0">
<div [class]="widget ? 'chart-widget' : 'chart'" [style]="{ height: widget ? (height + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
<div [class]="widget ? 'chart-widget' : 'chart'" *browserOnly [style]="{ height: widget ? (height + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>
@@ -102,7 +102,7 @@
<tr *ngFor="let pool of miningStats.pools">
<td class="d-none d-md-table-cell">{{ pool.rank }}</td>
<td class="text-right">
<img width="25" height="25" src="{{ pool.logo }}" [alt]="pool.name + ' mining pool logo'" onError="this.src = '/resources/mining-pools/default.svg'">
<img width="25" height="25" src="{{ pool.logo }}" [alt]="pool.name + ' mining pool logo'" onError="this.onerror=null; this.src = '/resources/mining-pools/default.svg'">
</td>
<td class="pool-name"><a [routerLink]="[('/mining/pool/' + pool.slug) | relativeUrl]">{{ pool.name }}</a></td>
<td class="" *ngIf="this.miningWindowPreference === '24h'">{{ pool.lastEstimatedHashrate }} {{

View File

@@ -41,7 +41,7 @@ export class PoolRankingComponent implements OnInit {
miningStatsObservable$: Observable<MiningStats>;
constructor(
private stateService: StateService,
public stateService: StateService,
private storageService: StorageService,
private formBuilder: UntypedFormBuilder,
private miningService: MiningService,

View File

@@ -25,7 +25,7 @@
</div>
</div>
<div class="row hash-chart full-width-row">
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartFinished)="onChartReady()"></div>
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartFinished)="onChartReady()"></div>
</div>
</div>

View File

@@ -6,7 +6,7 @@
<div *ngIf="poolStats$ | async as poolStats; else loadingMain">
<div style="display:flex" class="mb-3">
<img width="50" height="50" src="{{ poolStats['logo'] }}" [alt]="poolStats.pool.name + ' mining pool logo'"
onError="this.src = '/resources/mining-pools/default.svg'" class="mr-3">
onError="this.onerror=null; this.src = '/resources/mining-pools/default.svg'" class="mr-3">
<h1 class="m-0 pt-1 pt-md-0">{{ poolStats.pool.name }}</h1>
</div>
@@ -168,8 +168,8 @@
</ng-template>
<!-- Hashrate chart -->
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div>
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -35,4 +35,10 @@
</div>
</div>
<ng-container *serverOnly>
<!-- disgusting hack to apply an initial scroll to server-side rendered blockchain bar -->
<img *ngIf="!stateService.isLiquid()" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="pixel" style="visibility: hidden; position: absolute;" onload="(() => { const b = document.getElementById('blockchain-container'); const d = document.getElementById('divider'); if (b && d) { b.scrollLeft = d.getBoundingClientRect().x - (window.innerWidth * (window.innerWidth >= 768 ? 0.5 : 0.95)); }})()">
<img *ngIf="stateService.isLiquid()" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="pixel" style="visibility: hidden; position: absolute;" onload="(() => { const b = document.getElementById('blockchain-container'); const d = document.getElementById('divider'); if (b && d) { b.scrollLeft = d.getBoundingClientRect().x - (window.innerWidth >= 768 ? 420 : (window.innerWidth * 0.5)); }})()">
</ng-container>
<router-outlet></router-outlet>

View File

@@ -59,7 +59,7 @@ export class StartComponent implements OnInit, AfterViewChecked, OnDestroy {
hasMenu = false;
constructor(
private stateService: StateService,
public stateService: StateService,
private cd: ChangeDetectorRef,
) {
this.isiOS = ['iPhone','iPod','iPad'].includes((navigator as any)?.userAgentData?.platform || navigator.platform);

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, AfterViewInit, OnDestroy, HostListener, ViewChild, ElementRef } from '@angular/core';
import { Component, OnInit, AfterViewInit, OnDestroy, HostListener, ViewChild, ElementRef, Inject, ChangeDetectorRef } from '@angular/core';
import { ElectrsApiService } from '../../services/electrs-api.service';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import {
@@ -28,6 +28,7 @@ import { Price, PriceService } from '../../services/price.service';
import { isFeatureActive } from '../../bitcoin.utils';
import { ServicesApiServices } from '../../services/services-api.service';
import { EnterpriseService } from '../../services/enterprise.service';
import { ZONE_SERVICE } from '../../injection-tokens';
interface Pool {
id: number;
@@ -101,7 +102,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
inputIndex: number;
outputIndex: number;
graphExpanded: boolean = false;
graphWidth: number = 1000;
graphWidth: number = 1068;
graphHeight: number = 360;
inOutLimit: number = 150;
maxInOut: number = 0;
@@ -141,6 +142,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
private priceService: PriceService,
private storageService: StorageService,
private enterpriseService: EnterpriseService,
private cd: ChangeDetectorRef,
@Inject(ZONE_SERVICE) private zoneService: any,
) {}
ngOnInit() {
@@ -356,7 +359,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
}
});
this.subscription = this.route.paramMap
this.subscription = this.zoneService.wrapObservable(this.route.paramMap
.pipe(
switchMap((params: ParamMap) => {
const urlMatch = (params.get('id') || '').split(':');
@@ -430,7 +433,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
}
return of(tx);
})
)
))
.subscribe((tx: Transaction) => {
if (!tx) {
this.fetchCachedTx$.next(this.txId);
@@ -503,6 +506,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
).subscribe();
setTimeout(() => { this.applyFragment(); }, 0);
this.cd.detectChanges();
},
(error) => {
this.error = error;
@@ -785,9 +790,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
@HostListener('window:resize', ['$event'])
setGraphSize(): void {
this.isMobile = window.innerWidth < 850;
if (this.graphContainer?.nativeElement) {
if (this.graphContainer?.nativeElement && this.stateService.isBrowser) {
setTimeout(() => {
if (this.graphContainer?.nativeElement) {
if (this.graphContainer?.nativeElement?.clientWidth) {
this.graphWidth = this.graphContainer.nativeElement.clientWidth;
} else {
setTimeout(() => { this.setGraphSize(); }, 1);

View File

@@ -1,5 +1,13 @@
<div class="bowtie-graph">
<svg *ngIf="inputs && outputs" class="bowtie" [class.rtl]="dir === 'rtl'" [attr.height]="(height + 10) + 'px'" [attr.width]="width + 'px'">
<svg
*ngIf="inputs && outputs"
class="bowtie"
[class.rtl]="dir === 'rtl'"
[attr.height]="(height + 10) + 'px'"
[attr.width]="stateService.isBrowser ? (width + 'px') : '100%'"
[attr.viewBox]="stateService.isBrowser ? null : ('0 0 ' + width + ' ' + (height + 10))"
[attr.preserveAspectRatio]="stateService.isBrowser ? null : 'none'"
>
<defs>
<marker id="input-arrow" viewBox="-5 -5 10 10"
refX="0" refY="0"

View File

@@ -101,7 +101,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
constructor(
private router: Router,
private relativeUrlPipe: RelativeUrlPipe,
private stateService: StateService,
public stateService: StateService,
private electrsApiService: ElectrsApiService,
private assetsService: AssetsService,
@Inject(LOCALE_ID) private locale: string,