Merge branch 'master' into natsoni/hide-featured-assets-testnet
This commit is contained in:
		
						commit
						5537e79640
					
				| @ -98,11 +98,9 @@ | |||||||
|   </ng-template> |   </ng-template> | ||||||
| 
 | 
 | ||||||
|   <ng-template [ngIf]="error"> |   <ng-template [ngIf]="error"> | ||||||
|     <div class="text-center"> |     <app-http-error [error]="error"> | ||||||
|       Error loading address data. |       <span i18n="address.error.loading-address-data">Error loading address data.</span> | ||||||
|       <br> |     </app-http-error> | ||||||
|       <i>{{ error.error }}</i> |  | ||||||
|     </div> |  | ||||||
|   </ng-template> |   </ng-template> | ||||||
| 
 | 
 | ||||||
| </div> | </div> | ||||||
|  | |||||||
| @ -156,7 +156,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy { | |||||||
|           let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10))}</b><br>`; |           let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10))}</b><br>`; | ||||||
| 
 | 
 | ||||||
|           if (ticks[0].data[1] > 10_000_000) { |           if (ticks[0].data[1] > 10_000_000) { | ||||||
|             tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1] / 100_000_000, this.locale, '1.0-0')} BTC<br>`; |             tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1] / 100_000_000, this.locale, '1.0-8')} BTC<br>`; | ||||||
|           } else { |           } else { | ||||||
|             tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1], this.locale, '1.0-0')} sats<br>`; |             tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1], this.locale, '1.0-0')} sats<br>`; | ||||||
|           } |           } | ||||||
|  | |||||||
| @ -135,11 +135,10 @@ | |||||||
| 
 | 
 | ||||||
|   <ng-template [ngIf]="error"> |   <ng-template [ngIf]="error"> | ||||||
|     <br> |     <br> | ||||||
|  |     <ng-template [ngIf]="error.status === 413 || error.status === 405 || error.status === 504" [ngIfElse]="displayServerError"> | ||||||
|       <div class="text-center"> |       <div class="text-center"> | ||||||
|         <span i18n="address.error.loading-address-data">Error loading address data.</span> |         <span i18n="address.error.loading-address-data">Error loading address data.</span> | ||||||
|         <br> |         <br> | ||||||
|       <ng-template #displayServerError><i class="small">({{ error.error }})</i></ng-template> |  | ||||||
|       <ng-template [ngIf]="error.status === 413 || error.status === 405 || error.status === 504" [ngIfElse]="displayServerError"> |  | ||||||
|         <ng-container i18n="Electrum server limit exceeded error"> |         <ng-container i18n="Electrum server limit exceeded error"> | ||||||
|           <i>There many transactions on this address, more than your backend can handle. See more on <a href="/docs/faq#address-lookup-issues">setting up a stronger backend</a>.</i> |           <i>There many transactions on this address, more than your backend can handle. See more on <a href="/docs/faq#address-lookup-issues">setting up a stronger backend</a>.</i> | ||||||
|           <br><br> |           <br><br> | ||||||
| @ -150,10 +149,15 @@ | |||||||
|         <br> |         <br> | ||||||
|         <a href="http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}" target="_blank">http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}</a> |         <a href="http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}" target="_blank">http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}</a> | ||||||
|         <br><br> |         <br><br> | ||||||
|         <i class="small">({{ error.error }})</i> |         <i class="small">({{ error | httpErrorMsg }})</i> | ||||||
|       </ng-template> |  | ||||||
|       </div> |       </div> | ||||||
|     </ng-template> |     </ng-template> | ||||||
|  |     <ng-template #displayServerError> | ||||||
|  |       <app-http-error [error]="error"> | ||||||
|  |         <span i18n="address.error.loading-address-data">Error loading address data.</span> | ||||||
|  |       </app-http-error> | ||||||
|  |     </ng-template> | ||||||
|  |   </ng-template> | ||||||
| 
 | 
 | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -140,10 +140,22 @@ export class AddressComponent implements OnInit, OnDestroy { | |||||||
|           if (!fetchTxs.length) { |           if (!fetchTxs.length) { | ||||||
|             return of([]); |             return of([]); | ||||||
|           } |           } | ||||||
|           return this.apiService.getTransactionTimes$(fetchTxs); |           return this.apiService.getTransactionTimes$(fetchTxs).pipe( | ||||||
|  |             catchError((err) => { | ||||||
|  |               this.isLoadingAddress = false; | ||||||
|  |               this.isLoadingTransactions = false; | ||||||
|  |               this.error = err; | ||||||
|  |               this.seoService.logSoft404(); | ||||||
|  |               console.log(err); | ||||||
|  |               return of([]); | ||||||
|  |             }) | ||||||
|  |           ); | ||||||
|         }) |         }) | ||||||
|       ) |       ) | ||||||
|       .subscribe((times: number[]) => { |       .subscribe((times: number[] | null) => { | ||||||
|  |         if (!times) { | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|         times.forEach((time, index) => { |         times.forEach((time, index) => { | ||||||
|           this.tempTransactions[this.timeTxIndexes[index]].firstSeen = time; |           this.tempTransactions[this.timeTxIndexes[index]].firstSeen = time; | ||||||
|         }); |         }); | ||||||
|  | |||||||
| @ -146,13 +146,10 @@ | |||||||
|   </ng-template> |   </ng-template> | ||||||
| 
 | 
 | ||||||
|   <ng-template [ngIf]="error"> |   <ng-template [ngIf]="error"> | ||||||
|     <div class="text-center"> |     <app-http-error [error]="error"> | ||||||
|       <span i18n="asset.error.loading-asset-data">Error loading asset data.</span> |       <span i18n="asset.error.loading-asset-data">Error loading asset data.</span> | ||||||
|       <br> |     </app-http-error> | ||||||
|       <i>{{ error.error }}</i> |  | ||||||
|     </div> |  | ||||||
|   </ng-template> |   </ng-template> | ||||||
| 
 |  | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <br> | <br> | ||||||
|  | |||||||
| @ -46,9 +46,7 @@ | |||||||
| </ng-template> | </ng-template> | ||||||
| 
 | 
 | ||||||
| <ng-template [ngIf]="error"> | <ng-template [ngIf]="error"> | ||||||
|   <div class="text-center"> |   <app-http-error [error]="error"> | ||||||
|     <ng-container i18n="Asset data load error">Error loading assets data.</ng-container> |     <span i18n="Asset data load error">Error loading assets data.</span> | ||||||
|     <br> |   </app-http-error> | ||||||
|     <i>{{ error.error }}</i> |  | ||||||
|   </div> |  | ||||||
| </ng-template> | </ng-template> | ||||||
|  | |||||||
| @ -61,10 +61,10 @@ | |||||||
|         </td> |         </td> | ||||||
|       </tr> |       </tr> | ||||||
|       {{ activeFilters.rbf }} |       {{ activeFilters.rbf }} | ||||||
|       <tr *ngIf="(!auditEnabled && tx && tx.status === 'accelerated') || filters.length"> |       <tr *ngIf="(!auditEnabled && tx && (tx.status === 'accelerated' || tx.acc || acceleration)) || filters.length"> | ||||||
|         <td colspan="2"> |         <td colspan="2"> | ||||||
|           <div class="tags mt-2" [class.any-mode]="filterMode === 'or'"> |           <div class="tags mt-2" [class.any-mode]="filterMode === 'or'"> | ||||||
|             <span *ngIf="!auditEnabled && tx && tx.status === 'accelerated'" class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span> |             <span *ngIf="!auditEnabled && tx && (tx.status === 'accelerated' || tx.acc || acceleration)" class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span> | ||||||
|             <ng-container *ngFor="let filter of filters;"> |             <ng-container *ngFor="let filter of filters;"> | ||||||
|               <span class="btn badge filter-tag" [class.matching]="activeFilters[filter.key]">{{ filter.label }}</span> |               <span class="btn badge filter-tag" [class.matching]="activeFilters[filter.key]">{{ filter.label }}</span> | ||||||
|             </ng-container> |             </ng-container> | ||||||
|  | |||||||
| @ -9,8 +9,8 @@ | |||||||
|   justify-content: space-between; |   justify-content: space-between; | ||||||
|   padding: 10px 15px; |   padding: 10px 15px; | ||||||
|   text-align: left; |   text-align: left; | ||||||
|   min-width: 320px; |   min-width: 340px; | ||||||
|   max-width: 320px; |   max-width: 340px; | ||||||
|   pointer-events: none; |   pointer-events: none; | ||||||
|   z-index: 11; |   z-index: 11; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -61,8 +61,8 @@ export class BlockOverviewTooltipComponent implements OnChanges { | |||||||
|       this.vsize = this.tx.vsize || 1; |       this.vsize = this.tx.vsize || 1; | ||||||
|       this.feeRate = this.fee / this.vsize; |       this.feeRate = this.fee / this.vsize; | ||||||
|       this.effectiveRate = this.tx.rate; |       this.effectiveRate = this.tx.rate; | ||||||
|       this.acceleration = this.tx.acc; |  | ||||||
|       const txFlags = BigInt(this.tx.flags) || 0n; |       const txFlags = BigInt(this.tx.flags) || 0n; | ||||||
|  |       this.acceleration = this.tx.acc || (txFlags & TransactionFlags.acceleration); | ||||||
|       this.hasEffectiveRate = Math.abs((this.fee / this.vsize) - this.effectiveRate) > 0.05 |       this.hasEffectiveRate = Math.abs((this.fee / this.vsize) - this.effectiveRate) > 0.05 | ||||||
|         || (txFlags && (txFlags & (TransactionFlags.cpfp_child | TransactionFlags.cpfp_parent)) > 0n); |         || (txFlags && (txFlags & (TransactionFlags.cpfp_child | TransactionFlags.cpfp_parent)) > 0n); | ||||||
|       this.filters = this.tx.flags ? toFilters(txFlags).filter(f => f.tooltip) : []; |       this.filters = this.tx.flags ? toFilters(txFlags).filter(f => f.tooltip) : []; | ||||||
|  | |||||||
| @ -338,14 +338,12 @@ | |||||||
|     <app-transactions-list [transactions]="transactions" [paginated]="true" [blockTime]="block.timestamp"></app-transactions-list> |     <app-transactions-list [transactions]="transactions" [paginated]="true" [blockTime]="block.timestamp"></app-transactions-list> | ||||||
| 
 | 
 | ||||||
|     <ng-template [ngIf]="transactionsError"> |     <ng-template [ngIf]="transactionsError"> | ||||||
|       <div class="text-center"> |  | ||||||
|       <br> |       <br> | ||||||
|  |       <app-http-error [error]="transactionsError"> | ||||||
|         <span i18n="error.general-loading-data">Error loading data.</span> |         <span i18n="error.general-loading-data">Error loading data.</span> | ||||||
|         <br><br> |       </app-http-error> | ||||||
|         <i>{{ transactionsError.status }}: {{ transactionsError.error }}</i> |  | ||||||
|       <br> |       <br> | ||||||
|       <br> |       <br> | ||||||
|       </div> |  | ||||||
|     </ng-template> |     </ng-template> | ||||||
| 
 | 
 | ||||||
|     <ng-template [ngIf]="isLoadingTransactions && !transactionsError"> |     <ng-template [ngIf]="isLoadingTransactions && !transactionsError"> | ||||||
| @ -378,11 +376,9 @@ | |||||||
|     <br> |     <br> | ||||||
|   </ng-template> |   </ng-template> | ||||||
|   <ng-template [ngIf]="error"> |   <ng-template [ngIf]="error"> | ||||||
|     <div class="text-center"> |     <app-http-error [error]="error"> | ||||||
|       <span i18n="error.general-loading-data">Error loading data.</span> |       <span i18n="error.general-loading-data">Error loading data.</span> | ||||||
|       <br><br> |     </app-http-error> | ||||||
|       <i>{{ error.status }}: {{ error.error }}</i> |  | ||||||
|     </div> |  | ||||||
|   </ng-template> |   </ng-template> | ||||||
| 
 | 
 | ||||||
|   <ng-template #headerLoader> |   <ng-template #headerLoader> | ||||||
|  | |||||||
| @ -1,7 +1,15 @@ | |||||||
| <div *ngIf="showTitle" class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div> | <div *ngIf="showTitle && mode === 'difficulty'" class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div> | ||||||
|  | <div *ngIf="showTitle && mode === 'halving'" class="main-title" i18n="dashboard.halving-countdown">Halving Countdown</div> | ||||||
| <div class="card-wrapper"> | <div class="card-wrapper"> | ||||||
|   <div class="card"> |   <div class="card"> | ||||||
|     <div class="card-body more-padding"> |     <div class="widget-toggler"> | ||||||
|  |       <a href="" (click)="setMode('difficulty')" class="toggler-option" | ||||||
|  |         [ngClass]="{'inactive': mode === 'difficulty'}"><small i18n="statistics.average-small">difficulty</small></a> | ||||||
|  |       <span style="color: #ffffff66; font-size: 8px"> | </span> | ||||||
|  |       <a href="" (click)="setMode('halving')" class="toggler-option" | ||||||
|  |         [ngClass]="{'inactive': mode === 'halving'}"><small i18n="statistics.median-small">halving</small></a> | ||||||
|  |     </div> | ||||||
|  |     <div *ngIf="mode === 'difficulty'; else halving" class="card-body more-padding"> | ||||||
|       <div class="difficulty-adjustment-container" *ngIf="(isLoadingWebSocket$ | async) === false && (difficultyEpoch$ | async) as epochData; else loadingDifficulty"> |       <div class="difficulty-adjustment-container" *ngIf="(isLoadingWebSocket$ | async) === false && (difficultyEpoch$ | async) as epochData; else loadingDifficulty"> | ||||||
|         <div class="epoch-progress"> |         <div class="epoch-progress"> | ||||||
|           <svg #epochSvg class="epoch-blocks" height="22px" width="100%" viewBox="0 0 224 9" shape-rendering="crispEdges" preserveAspectRatio="none"> |           <svg #epochSvg class="epoch-blocks" height="22px" width="100%" viewBox="0 0 224 9" shape-rendering="crispEdges" preserveAspectRatio="none"> | ||||||
| @ -76,6 +84,52 @@ | |||||||
|   </div> |   </div> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
|  | <ng-template #halving> | ||||||
|  |   <div class="card-body more-padding"> | ||||||
|  |     <div class="difficulty-adjustment-container halving" *ngIf="(isLoadingWebSocket$ | async) === false && (difficultyEpoch$ | async) as epochData; else loadingDifficulty"> | ||||||
|  |       <div class="halving-progress"> | ||||||
|  |         <div class="background"></div> | ||||||
|  |         <div class="remaining" [style]="{ left: ((210000 - epochData.blocksUntilHalving) / 2100).toFixed(2) + '%' }"></div> | ||||||
|  |         <div class="label"> | ||||||
|  |           {{ ((210000 - epochData.blocksUntilHalving) / 2100).toFixed(2) }}% | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="difficulty-stats"> | ||||||
|  |         <div class="item"> | ||||||
|  |           <div class="card-text bigger"> | ||||||
|  |             <app-btc [satoshis]="312500000"></app-btc> | ||||||
|  |           </div> | ||||||
|  |           <div class="symbol"> | ||||||
|  |             <span i18n="difficulty-box.new-subsidy">New subsidy</span> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="item"> | ||||||
|  |           <div class="card-text"> | ||||||
|  |             {{ epochData.blocksUntilHalving | number }} | ||||||
|  |           </div> | ||||||
|  |           <div class="symbol"> | ||||||
|  |             <span *ngIf="epochData.blocksUntilHalving > 1" i18n="shared.blocks-remaining">Blocks remaining</span> | ||||||
|  |             <span *ngIf="epochData.blocksUntilHalving === 1" i18n="shared.block-remaining">Block remaining</span> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="item"> | ||||||
|  |           <div class="card-text" i18n-ngbTooltip="mining.average-fee" placement="bottom"> | ||||||
|  |             <span>{{ epochData.timeUntilHalving | date }}</span> | ||||||
|  |           </div> | ||||||
|  |           <div class="symbol" *ngIf="epochData.blocksUntilHalving === 1; else approxTime"> | ||||||
|  |             <app-time kind="until" [time]="epochData.adjustedTimeAvg + now" [fastRender]="false" [fixedRender]="true" [precision]="1" minUnit="minute"></app-time> | ||||||
|  |           </div> | ||||||
|  |           <ng-template #approxTime> | ||||||
|  |             <div class="symbol"> | ||||||
|  |               <app-time kind="until" [time]="epochData.timeUntilHalving" [fastRender]="false" [fixedRender]="true" [precision]="0" [numUnits]="2" [units]="['year', 'day', 'hour', 'minute']"></app-time> | ||||||
|  |             </div> | ||||||
|  |           </ng-template> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </ng-template> | ||||||
|  | 
 | ||||||
| <ng-template #loadingDifficulty> | <ng-template #loadingDifficulty> | ||||||
|   <div class="epoch-progress"> |   <div class="epoch-progress"> | ||||||
|     <div class="skeleton-loader"></div> |     <div class="skeleton-loader"></div> | ||||||
|  | |||||||
| @ -168,7 +168,7 @@ | |||||||
|   white-space: nowrap; |   white-space: nowrap; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .epoch-progress { | .epoch-progress, .halving-progress { | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   height: 22px; |   height: 22px; | ||||||
|   margin-bottom: 12px; |   margin-bottom: 12px; | ||||||
| @ -213,3 +213,42 @@ | |||||||
| .blocks-behind { | .blocks-behind { | ||||||
|   color: #D81B60; |   color: #D81B60; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .halving-progress { | ||||||
|  |   position: relative; | ||||||
|  |   .background, .remaining { | ||||||
|  |     position: absolute; | ||||||
|  |     top: 0; | ||||||
|  |     bottom: 0; | ||||||
|  |     height: 100%; | ||||||
|  |   } | ||||||
|  |   .background { | ||||||
|  |     background: linear-gradient(to right, #105fb0, #9339f4); | ||||||
|  |     left: 0; | ||||||
|  |     right: 0; | ||||||
|  |   } | ||||||
|  |   .remaining { | ||||||
|  |     background: #2d3348; | ||||||
|  |     right: 0; | ||||||
|  |   } | ||||||
|  |   .label { | ||||||
|  |     position: relative; | ||||||
|  |     margin: auto; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .widget-toggler { | ||||||
|  |   font-size: 12px; | ||||||
|  |   position: absolute; | ||||||
|  |   top: -20px; | ||||||
|  |   right: 3px; | ||||||
|  |   text-align: right; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .toggler-option { | ||||||
|  |   text-decoration: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .inactive { | ||||||
|  |   color: #ffffff66; | ||||||
|  | } | ||||||
| @ -51,6 +51,10 @@ export class DifficultyComponent implements OnInit { | |||||||
|   isLoadingWebSocket$: Observable<boolean>; |   isLoadingWebSocket$: Observable<boolean>; | ||||||
|   difficultyEpoch$: Observable<EpochProgress>; |   difficultyEpoch$: Observable<EpochProgress>; | ||||||
| 
 | 
 | ||||||
|  |   mode: 'difficulty' | 'halving' = 'difficulty'; | ||||||
|  |   userSelectedMode: boolean = false; | ||||||
|  | 
 | ||||||
|  |   now: number = Date.now(); | ||||||
|   epochStart: number; |   epochStart: number; | ||||||
|   currentHeight: number; |   currentHeight: number; | ||||||
|   currentIndex: number; |   currentIndex: number; | ||||||
| @ -101,6 +105,11 @@ export class DifficultyComponent implements OnInit { | |||||||
|         const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000); |         const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000); | ||||||
|         const newEpochStart = Math.floor(this.stateService.latestBlockHeight / EPOCH_BLOCK_LENGTH) * EPOCH_BLOCK_LENGTH; |         const newEpochStart = Math.floor(this.stateService.latestBlockHeight / EPOCH_BLOCK_LENGTH) * EPOCH_BLOCK_LENGTH; | ||||||
|         const newExpectedHeight = Math.floor(newEpochStart + da.expectedBlocks); |         const newExpectedHeight = Math.floor(newEpochStart + da.expectedBlocks); | ||||||
|  |         this.now = new Date().getTime(); | ||||||
|  | 
 | ||||||
|  |         if (blocksUntilHalving < da.remainingBlocks && !this.userSelectedMode) { | ||||||
|  |           this.mode = 'halving'; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         if (newEpochStart !== this.epochStart || newExpectedHeight !== this.expectedHeight || this.currentHeight !== this.stateService.latestBlockHeight) { |         if (newEpochStart !== this.epochStart || newExpectedHeight !== this.expectedHeight || this.currentHeight !== this.stateService.latestBlockHeight) { | ||||||
|           this.epochStart = newEpochStart; |           this.epochStart = newEpochStart; | ||||||
| @ -194,6 +203,12 @@ export class DifficultyComponent implements OnInit { | |||||||
|     return shapes; |     return shapes; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   setMode(mode: 'difficulty' | 'halving'): boolean { | ||||||
|  |     this.mode = mode; | ||||||
|  |     this.userSelectedMode = true; | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   @HostListener('pointerdown', ['$event']) |   @HostListener('pointerdown', ['$event']) | ||||||
|   onPointerDown(event): void { |   onPointerDown(event): void { | ||||||
|     if (this.epochSvgElement?.nativeElement?.contains(event.target)) { |     if (this.epochSvgElement?.nativeElement?.contains(event.target)) { | ||||||
|  | |||||||
| @ -32,6 +32,7 @@ | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .chart { | .chart { | ||||||
|  |   margin-top: 10px; | ||||||
|   margin-bottom: 20px; |   margin-bottom: 20px; | ||||||
|   @media (max-width: 768px) { |   @media (max-width: 768px) { | ||||||
|     margin-bottom: 10px; |     margin-bottom: 10px; | ||||||
|  | |||||||
| @ -65,7 +65,9 @@ export class PoolComponent implements OnInit { | |||||||
|             .pipe( |             .pipe( | ||||||
|               switchMap((data) => { |               switchMap((data) => { | ||||||
|                 this.isLoading = false; |                 this.isLoading = false; | ||||||
|                 this.prepareChartOptions(data.map(val => [val.timestamp * 1000, val.avgHashrate])); |                 const hashrate = data.map(val => [val.timestamp * 1000, val.avgHashrate]); | ||||||
|  |                 const share = data.map(val => [val.timestamp * 1000, val.share * 100]); | ||||||
|  |                 this.prepareChartOptions(hashrate, share); | ||||||
|                 return [slug]; |                 return [slug]; | ||||||
|               }), |               }), | ||||||
|               catchError(() => { |               catchError(() => { | ||||||
| @ -130,9 +132,9 @@ export class PoolComponent implements OnInit { | |||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   prepareChartOptions(data) { |   prepareChartOptions(hashrate, share) { | ||||||
|     let title: object; |     let title: object; | ||||||
|     if (data.length <= 1) { |     if (hashrate.length <= 1) { | ||||||
|       title = { |       title = { | ||||||
|         textStyle: { |         textStyle: { | ||||||
|           color: 'grey', |           color: 'grey', | ||||||
| @ -177,26 +179,57 @@ export class PoolComponent implements OnInit { | |||||||
|         }, |         }, | ||||||
|         borderColor: '#000', |         borderColor: '#000', | ||||||
|         formatter: function (ticks: any[]) { |         formatter: function (ticks: any[]) { | ||||||
|           let hashratePowerOfTen: any = selectPowerOfTen(1); |           let hashrateString = ''; | ||||||
|           let hashrate = ticks[0].data[1]; |           let dominanceString = ''; | ||||||
| 
 | 
 | ||||||
|           hashratePowerOfTen = selectPowerOfTen(ticks[0].data[1], 10); |           for (const tick of ticks) { | ||||||
|           hashrate = ticks[0].data[1] / hashratePowerOfTen.divider; |             if (tick.seriesIndex === 0) { | ||||||
|  |               let hashratePowerOfTen = selectPowerOfTen(tick.data[1], 10); | ||||||
|  |               let hashrateData = tick.data[1] / hashratePowerOfTen.divider; | ||||||
|  |               hashrateString = `${tick.marker} ${tick.seriesName}: ${formatNumber(hashrateData, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s<br>`; | ||||||
|  |             } else if (tick.seriesIndex === 1) { | ||||||
|  |               dominanceString = `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.0-2')}%`; | ||||||
|  |             }              | ||||||
|  |           } | ||||||
|            |            | ||||||
|           return ` |           return ` | ||||||
|             <b style="color: white; margin-left: 18px">${ticks[0].axisValueLabel}</b><br> |             <b style="color: white; margin-left: 18px">${ticks[0].axisValueLabel}</b><br> | ||||||
|             <span>${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s</span><br> |             <span>${hashrateString}</span> | ||||||
|  |             <span>${dominanceString}</span> | ||||||
|           `;
 |           `;
 | ||||||
|         }.bind(this) |         }.bind(this) | ||||||
|       }, |       }, | ||||||
|       xAxis: data.length <= 1 ? undefined : { |       xAxis: hashrate.length <= 1 ? undefined : { | ||||||
|         type: 'time', |         type: 'time', | ||||||
|         splitNumber: (this.isMobile()) ? 5 : 10, |         splitNumber: (this.isMobile()) ? 5 : 10, | ||||||
|         axisLabel: { |         axisLabel: { | ||||||
|           hideOverlap: true, |           hideOverlap: true, | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|       yAxis: data.length <= 1 ? undefined : [ |       legend: { | ||||||
|  |         data: [ | ||||||
|  |           { | ||||||
|  |             name: $localize`:mining.hashrate:Hashrate`, | ||||||
|  |             inactiveColor: 'rgb(110, 112, 121)', | ||||||
|  |             textStyle: { | ||||||
|  |               color: 'white', | ||||||
|  |             }, | ||||||
|  |             icon: 'roundRect', | ||||||
|  |             itemStyle: { | ||||||
|  |               color: '#FFB300', | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: $localize`:mining.pool-dominance:Pool Dominance`, | ||||||
|  |             inactiveColor: 'rgb(110, 112, 121)', | ||||||
|  |             textStyle: { | ||||||
|  |               color: 'white', | ||||||
|  |             }, | ||||||
|  |             icon: 'roundRect', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |       yAxis: hashrate.length <= 1 ? undefined : [ | ||||||
|         { |         { | ||||||
|           min: (value) => { |           min: (value) => { | ||||||
|             return value.min * 0.9; |             return value.min * 0.9; | ||||||
| @ -214,21 +247,45 @@ export class PoolComponent implements OnInit { | |||||||
|             show: false, |             show: false, | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|       ], |  | ||||||
|       series: data.length <= 1 ? undefined : [ |  | ||||||
|         { |         { | ||||||
|           zlevel: 0, |           type: 'value', | ||||||
|           name: 'Hashrate', |           axisLabel: { | ||||||
|  |             color: 'rgb(110, 112, 121)', | ||||||
|  |             formatter: (val) => { | ||||||
|  |               return `${val}%` | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           splitLine: { | ||||||
|  |             show: false, | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       ], | ||||||
|  |       series: hashrate.length <= 1 ? undefined : [ | ||||||
|  |         { | ||||||
|  |           zlevel: 1, | ||||||
|  |           name: $localize`:mining.hashrate:Hashrate`, | ||||||
|           showSymbol: false, |           showSymbol: false, | ||||||
|           symbol: 'none', |           symbol: 'none', | ||||||
|           data: data, |           data: hashrate, | ||||||
|           type: 'line', |           type: 'line', | ||||||
|           lineStyle: { |           lineStyle: { | ||||||
|             width: 2, |             width: 2, | ||||||
|           }, |           }, | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |           zlevel: 0, | ||||||
|  |           name: $localize`:mining.pool-dominance:Pool Dominance`, | ||||||
|  |           showSymbol: false, | ||||||
|  |           symbol: 'none', | ||||||
|  |           data: share, | ||||||
|  |           type: 'line', | ||||||
|  |           yAxisIndex: 1, | ||||||
|  |           lineStyle: { | ||||||
|  |             width: 2, | ||||||
|  |           }, | ||||||
|  |         } | ||||||
|       ], |       ], | ||||||
|       dataZoom: data.length <= 1 ? undefined : [{ |       dataZoom: hashrate.length <= 1 ? undefined : [{ | ||||||
|         type: 'inside', |         type: 'inside', | ||||||
|         realtime: true, |         realtime: true, | ||||||
|         zoomLock: true, |         zoomLock: true, | ||||||
|  | |||||||
| @ -74,8 +74,7 @@ | |||||||
|                   <td class="td-width" i18n="transaction.audit">Audit</td> |                   <td class="td-width" i18n="transaction.audit">Audit</td> | ||||||
|                   <td *ngIf="pool" class="wrap-cell"> |                   <td *ngIf="pool" class="wrap-cell"> | ||||||
|                     <ng-container *ngIf="auditStatus"> |                     <ng-container *ngIf="auditStatus"> | ||||||
|                       <span *ngIf="auditStatus.coinbase; else accelerated" class="badge badge-primary mr-1" i18n="tx-features.tag.coinbase|Coinbase">Coinbase</span> |                       <span *ngIf="auditStatus.coinbase; else expected" class="badge badge-primary mr-1" i18n="tx-features.tag.coinbase|Coinbase">Coinbase</span> | ||||||
|                       <ng-template #accelerated><span *ngIf="auditStatus.accelerated || accelerationInfo || (tx && tx.acceleration) ; else expected" class="badge badge-accelerated mr-1" i18n="transaction.audit.accelerated">Accelerated</span></ng-template> |  | ||||||
|                       <ng-template #expected><span *ngIf="auditStatus.expected; else seen" class="badge badge-success mr-1" i18n-ngbTooltip="Expected in block tooltip" ngbTooltip="This transaction was projected to be included in the block" placement="bottom" i18n="tx-features.tag.expected|Expected in Block">Expected in Block</span></ng-template> |                       <ng-template #expected><span *ngIf="auditStatus.expected; else seen" class="badge badge-success mr-1" i18n-ngbTooltip="Expected in block tooltip" ngbTooltip="This transaction was projected to be included in the block" placement="bottom" i18n="tx-features.tag.expected|Expected in Block">Expected in Block</span></ng-template> | ||||||
|                       <ng-template #seen><span *ngIf="auditStatus.seen; else notSeen" class="badge badge-success mr-1" i18n-ngbTooltip="Seen in mempool tooltip" ngbTooltip="This transaction was seen in the mempool prior to mining" placement="bottom" i18n="tx-features.tag.seen|Seen in Mempool">Seen in Mempool</span></ng-template> |                       <ng-template #seen><span *ngIf="auditStatus.seen; else notSeen" class="badge badge-success mr-1" i18n-ngbTooltip="Seen in mempool tooltip" ngbTooltip="This transaction was seen in the mempool prior to mining" placement="bottom" i18n="tx-features.tag.seen|Seen in Mempool">Seen in Mempool</span></ng-template> | ||||||
|                       <ng-template #notSeen><span class="badge badge-warning mr-1" i18n-ngbTooltip="Not seen in mempool tooltip" ngbTooltip="This transaction was missing from our mempool prior to mining" placement="bottom" i18n="tx-features.tag.not-seen|Not seen in Mempool">Not seen in Mempool</span></ng-template> |                       <ng-template #notSeen><span class="badge badge-warning mr-1" i18n-ngbTooltip="Not seen in mempool tooltip" ngbTooltip="This transaction was missing from our mempool prior to mining" placement="bottom" i18n="tx-features.tag.not-seen|Not seen in Mempool">Not seen in Mempool</span></ng-template> | ||||||
| @ -517,9 +516,9 @@ | |||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     <ng-template #errorTemplate> |     <ng-template #errorTemplate> | ||||||
|       <div class="text-center"> |       <app-http-error [error]="error"> | ||||||
|         <h3>{{ error.error }}</h3> |         <span i18n="transaction.error.loading-transaction-data">Error loading transaction data.</span> | ||||||
|       </div> |       </app-http-error> | ||||||
|     </ng-template> |     </ng-template> | ||||||
|   </ng-template> |   </ng-template> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -66,9 +66,7 @@ | |||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <ng-template [ngIf]="error"> | <ng-template [ngIf]="error"> | ||||||
|   <div class="text-center"> |   <app-http-error [error]="error"> | ||||||
|     <span i18n="error.general-loading-data">Error loading data.</span> |     <span i18n="error.general-loading-data">Error loading data.</span> | ||||||
|     <br><br> |   </app-http-error> | ||||||
|     <i>{{ error.status }}: {{ error.error }}</i> |  | ||||||
|   </div> |  | ||||||
| </ng-template> | </ng-template> | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; | import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; | ||||||
| import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler, HttpResponse, HttpHeaders } from '@angular/common/http'; | import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http'; | ||||||
| import { Observable, of } from 'rxjs'; | import { Observable, of } from 'rxjs'; | ||||||
| import { tap } from 'rxjs/operators'; | import { catchError, tap } from 'rxjs/operators'; | ||||||
| import { TransferState, makeStateKey } from '@angular/platform-browser'; | import { TransferState, makeStateKey } from '@angular/platform-browser'; | ||||||
| import { isPlatformBrowser } from '@angular/common'; | import { isPlatformBrowser } from '@angular/common'; | ||||||
| 
 | 
 | ||||||
| @ -36,7 +36,8 @@ export class HttpCacheInterceptor implements HttpInterceptor { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return next.handle(request) |     return next.handle(request) | ||||||
|       .pipe(tap((event: HttpEvent<any>) => { |       .pipe( | ||||||
|  |         tap((event: HttpEvent<any>) => { | ||||||
|           if (!this.isBrowser && event instanceof HttpResponse) { |           if (!this.isBrowser && event instanceof HttpResponse) { | ||||||
|             let keyId = request.url.split('/').slice(3).join('/'); |             let keyId = request.url.split('/').slice(3).join('/'); | ||||||
|             const headers = {}; |             const headers = {}; | ||||||
| @ -45,6 +46,31 @@ export class HttpCacheInterceptor implements HttpInterceptor { | |||||||
|             } |             } | ||||||
|             this.transferState.set<any>(makeStateKey('/' + keyId), { response: event, headers }); |             this.transferState.set<any>(makeStateKey('/' + keyId), { response: event, headers }); | ||||||
|           } |           } | ||||||
|       })); |         }), | ||||||
|  |         catchError((e) => { | ||||||
|  |           if (e instanceof HttpErrorResponse) { | ||||||
|  |             if (e.status === 0) { | ||||||
|  |               throw new HttpErrorResponse({ | ||||||
|  |                 error: 'Unknown error', | ||||||
|  |                 headers: e.headers, | ||||||
|  |                 status: 0, | ||||||
|  |                 statusText: 'Unknown error', | ||||||
|  |                 url: e.url, | ||||||
|  |               }); | ||||||
|  |             } else { | ||||||
|  |               throw e; | ||||||
|  |             } | ||||||
|  |           } else { | ||||||
|  |             const msg = e?.['message'] || 'Unknown error'; | ||||||
|  |             throw new HttpErrorResponse({ | ||||||
|  |               error: msg, | ||||||
|  |               headers: new HttpHeaders(), | ||||||
|  |               status: 0, | ||||||
|  |               statusText: msg, | ||||||
|  |               url: '', | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,4 @@ | |||||||
|  | <div class="http-error"> | ||||||
|  |   <p><b><ng-content></ng-content></b></p> | ||||||
|  |   <i class="small">({{ error | httpErrorMsg }})</i> | ||||||
|  | </div> | ||||||
| @ -0,0 +1,5 @@ | |||||||
|  | .http-error { | ||||||
|  |   width: 100%; | ||||||
|  |   margin: 1em auto; | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
| @ -0,0 +1,11 @@ | |||||||
|  | import { HttpErrorResponse } from '@angular/common/http'; | ||||||
|  | import { Component, Input } from '@angular/core'; | ||||||
|  | 
 | ||||||
|  | @Component({ | ||||||
|  |   selector: 'app-http-error', | ||||||
|  |   templateUrl: './http-error.component.html', | ||||||
|  |   styleUrls: ['./http-error.component.scss'] | ||||||
|  | }) | ||||||
|  | export class HttpErrorComponent { | ||||||
|  |   @Input() error: HttpErrorResponse | null; | ||||||
|  | } | ||||||
| @ -0,0 +1,9 @@ | |||||||
|  | import { HttpErrorResponse } from '@angular/common/http'; | ||||||
|  | import { Pipe, PipeTransform } from '@angular/core'; | ||||||
|  | 
 | ||||||
|  | @Pipe({ name: 'httpErrorMsg' }) | ||||||
|  | export class HttpErrorPipe implements PipeTransform { | ||||||
|  |   transform(e: HttpErrorResponse | null): string { | ||||||
|  |     return e ? `${e.status}: ${e.statusText}` : ''; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -21,6 +21,7 @@ import { ScriptpubkeyTypePipe } from './pipes/scriptpubkey-type-pipe/scriptpubke | |||||||
| import { BytesPipe } from './pipes/bytes-pipe/bytes.pipe'; | import { BytesPipe } from './pipes/bytes-pipe/bytes.pipe'; | ||||||
| import { WuBytesPipe } from './pipes/bytes-pipe/wubytes.pipe'; | import { WuBytesPipe } from './pipes/bytes-pipe/wubytes.pipe'; | ||||||
| import { FiatCurrencyPipe } from './pipes/fiat-currency.pipe'; | import { FiatCurrencyPipe } from './pipes/fiat-currency.pipe'; | ||||||
|  | import { HttpErrorPipe } from './pipes/http-error-pipe/http-error.pipe'; | ||||||
| import { BlockchainComponent } from '../components/blockchain/blockchain.component'; | import { BlockchainComponent } from '../components/blockchain/blockchain.component'; | ||||||
| import { TimeComponent } from '../components/time/time.component'; | import { TimeComponent } from '../components/time/time.component'; | ||||||
| import { ClipboardComponent } from '../components/clipboard/clipboard.component'; | import { ClipboardComponent } from '../components/clipboard/clipboard.component'; | ||||||
| @ -104,6 +105,7 @@ import { ClockFaceComponent } from '../components/clock-face/clock-face.componen | |||||||
| import { ClockComponent } from '../components/clock/clock.component'; | import { ClockComponent } from '../components/clock/clock.component'; | ||||||
| import { CalculatorComponent } from '../components/calculator/calculator.component'; | import { CalculatorComponent } from '../components/calculator/calculator.component'; | ||||||
| import { BitcoinsatoshisPipe } from '../shared/pipes/bitcoinsatoshis.pipe'; | import { BitcoinsatoshisPipe } from '../shared/pipes/bitcoinsatoshis.pipe'; | ||||||
|  | import { HttpErrorComponent } from '../shared/components/http-error/http-error.component'; | ||||||
| 
 | 
 | ||||||
| import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-directives/weight-directives'; | import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-directives/weight-directives'; | ||||||
| 
 | 
 | ||||||
| @ -133,6 +135,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir | |||||||
|     Decimal2HexPipe, |     Decimal2HexPipe, | ||||||
|     FeeRoundingPipe, |     FeeRoundingPipe, | ||||||
|     FiatCurrencyPipe, |     FiatCurrencyPipe, | ||||||
|  |     HttpErrorPipe, | ||||||
|     ColoredPriceDirective, |     ColoredPriceDirective, | ||||||
|     BrowserOnlyDirective, |     BrowserOnlyDirective, | ||||||
|     ServerOnlyDirective, |     ServerOnlyDirective, | ||||||
| @ -208,6 +211,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir | |||||||
|     AccelerationsListComponent, |     AccelerationsListComponent, | ||||||
|     AccelerationStatsComponent, |     AccelerationStatsComponent, | ||||||
|     PendingStatsComponent, |     PendingStatsComponent, | ||||||
|  |     HttpErrorComponent, | ||||||
|   ], |   ], | ||||||
|   imports: [ |   imports: [ | ||||||
|     CommonModule, |     CommonModule, | ||||||
| @ -262,6 +266,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir | |||||||
|     VbytesPipe, |     VbytesPipe, | ||||||
|     WuBytesPipe, |     WuBytesPipe, | ||||||
|     FiatCurrencyPipe, |     FiatCurrencyPipe, | ||||||
|  |     HttpErrorPipe, | ||||||
|     CeilPipe, |     CeilPipe, | ||||||
|     ShortenStringPipe, |     ShortenStringPipe, | ||||||
|     CapAddressPipe, |     CapAddressPipe, | ||||||
| @ -327,6 +332,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir | |||||||
|     AccelerationsListComponent, |     AccelerationsListComponent, | ||||||
|     AccelerationStatsComponent, |     AccelerationStatsComponent, | ||||||
|     PendingStatsComponent, |     PendingStatsComponent, | ||||||
|  |     HttpErrorComponent, | ||||||
| 
 | 
 | ||||||
|     MempoolBlockOverviewComponent, |     MempoolBlockOverviewComponent, | ||||||
|     ClockchainComponent, |     ClockchainComponent, | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user