Merge branch 'master' into natsoni/hide-featured-assets-testnet
This commit is contained in:
		
						commit
						5537e79640
					
				| @ -98,11 +98,9 @@ | ||||
|   </ng-template> | ||||
| 
 | ||||
|   <ng-template [ngIf]="error"> | ||||
|     <div class="text-center"> | ||||
|       Error loading address data. | ||||
|       <br> | ||||
|       <i>{{ error.error }}</i> | ||||
|     </div> | ||||
|     <app-http-error [error]="error"> | ||||
|       <span i18n="address.error.loading-address-data">Error loading address data.</span> | ||||
|     </app-http-error> | ||||
|   </ng-template> | ||||
| 
 | ||||
| </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>`; | ||||
| 
 | ||||
|           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 { | ||||
|             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"> | ||||
|     <br> | ||||
|     <ng-template [ngIf]="error.status === 413 || error.status === 405 || error.status === 504" [ngIfElse]="displayServerError"> | ||||
|       <div class="text-center"> | ||||
|         <span i18n="address.error.loading-address-data">Error loading address data.</span> | ||||
|         <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"> | ||||
|           <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> | ||||
| @ -150,10 +149,15 @@ | ||||
|         <br> | ||||
|         <a href="http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}" target="_blank">http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}</a> | ||||
|         <br><br> | ||||
|         <i class="small">({{ error.error }})</i> | ||||
|       </ng-template> | ||||
|         <i class="small">({{ error | httpErrorMsg }})</i> | ||||
|       </div> | ||||
|     </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> | ||||
| 
 | ||||
|  | ||||
| @ -140,10 +140,22 @@ export class AddressComponent implements OnInit, OnDestroy { | ||||
|           if (!fetchTxs.length) { | ||||
|             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) => { | ||||
|           this.tempTransactions[this.timeTxIndexes[index]].firstSeen = time; | ||||
|         }); | ||||
|  | ||||
| @ -146,13 +146,10 @@ | ||||
|   </ng-template> | ||||
| 
 | ||||
|   <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> | ||||
|       <br> | ||||
|       <i>{{ error.error }}</i> | ||||
|     </div> | ||||
|     </app-http-error> | ||||
|   </ng-template> | ||||
| 
 | ||||
| </div> | ||||
| 
 | ||||
| <br> | ||||
|  | ||||
| @ -46,9 +46,7 @@ | ||||
| </ng-template> | ||||
| 
 | ||||
| <ng-template [ngIf]="error"> | ||||
|   <div class="text-center"> | ||||
|     <ng-container i18n="Asset data load error">Error loading assets data.</ng-container> | ||||
|     <br> | ||||
|     <i>{{ error.error }}</i> | ||||
|   </div> | ||||
|   <app-http-error [error]="error"> | ||||
|     <span i18n="Asset data load error">Error loading assets data.</span> | ||||
|   </app-http-error> | ||||
| </ng-template> | ||||
|  | ||||
| @ -61,10 +61,10 @@ | ||||
|         </td> | ||||
|       </tr> | ||||
|       {{ 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"> | ||||
|           <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;"> | ||||
|               <span class="btn badge filter-tag" [class.matching]="activeFilters[filter.key]">{{ filter.label }}</span> | ||||
|             </ng-container> | ||||
|  | ||||
| @ -9,8 +9,8 @@ | ||||
|   justify-content: space-between; | ||||
|   padding: 10px 15px; | ||||
|   text-align: left; | ||||
|   min-width: 320px; | ||||
|   max-width: 320px; | ||||
|   min-width: 340px; | ||||
|   max-width: 340px; | ||||
|   pointer-events: none; | ||||
|   z-index: 11; | ||||
| 
 | ||||
|  | ||||
| @ -61,8 +61,8 @@ export class BlockOverviewTooltipComponent implements OnChanges { | ||||
|       this.vsize = this.tx.vsize || 1; | ||||
|       this.feeRate = this.fee / this.vsize; | ||||
|       this.effectiveRate = this.tx.rate; | ||||
|       this.acceleration = this.tx.acc; | ||||
|       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 | ||||
|         || (txFlags && (txFlags & (TransactionFlags.cpfp_child | TransactionFlags.cpfp_parent)) > 0n); | ||||
|       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> | ||||
| 
 | ||||
|     <ng-template [ngIf]="transactionsError"> | ||||
|       <div class="text-center"> | ||||
|       <br> | ||||
|       <app-http-error [error]="transactionsError"> | ||||
|         <span i18n="error.general-loading-data">Error loading data.</span> | ||||
|         <br><br> | ||||
|         <i>{{ transactionsError.status }}: {{ transactionsError.error }}</i> | ||||
|       </app-http-error> | ||||
|       <br> | ||||
|       <br> | ||||
|       </div> | ||||
|     </ng-template> | ||||
| 
 | ||||
|     <ng-template [ngIf]="isLoadingTransactions && !transactionsError"> | ||||
| @ -378,11 +376,9 @@ | ||||
|     <br> | ||||
|   </ng-template> | ||||
|   <ng-template [ngIf]="error"> | ||||
|     <div class="text-center"> | ||||
|     <app-http-error [error]="error"> | ||||
|       <span i18n="error.general-loading-data">Error loading data.</span> | ||||
|       <br><br> | ||||
|       <i>{{ error.status }}: {{ error.error }}</i> | ||||
|     </div> | ||||
|     </app-http-error> | ||||
|   </ng-template> | ||||
| 
 | ||||
|   <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"> | ||||
|     <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="epoch-progress"> | ||||
|           <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> | ||||
| 
 | ||||
| <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> | ||||
|   <div class="epoch-progress"> | ||||
|     <div class="skeleton-loader"></div> | ||||
|  | ||||
| @ -168,7 +168,7 @@ | ||||
|   white-space: nowrap; | ||||
| } | ||||
| 
 | ||||
| .epoch-progress { | ||||
| .epoch-progress, .halving-progress { | ||||
|   width: 100%; | ||||
|   height: 22px; | ||||
|   margin-bottom: 12px; | ||||
| @ -213,3 +213,42 @@ | ||||
| .blocks-behind { | ||||
|   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>; | ||||
|   difficultyEpoch$: Observable<EpochProgress>; | ||||
| 
 | ||||
|   mode: 'difficulty' | 'halving' = 'difficulty'; | ||||
|   userSelectedMode: boolean = false; | ||||
| 
 | ||||
|   now: number = Date.now(); | ||||
|   epochStart: number; | ||||
|   currentHeight: number; | ||||
|   currentIndex: number; | ||||
| @ -101,6 +105,11 @@ export class DifficultyComponent implements OnInit { | ||||
|         const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000); | ||||
|         const newEpochStart = Math.floor(this.stateService.latestBlockHeight / EPOCH_BLOCK_LENGTH) * EPOCH_BLOCK_LENGTH; | ||||
|         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) { | ||||
|           this.epochStart = newEpochStart; | ||||
| @ -194,6 +203,12 @@ export class DifficultyComponent implements OnInit { | ||||
|     return shapes; | ||||
|   } | ||||
| 
 | ||||
|   setMode(mode: 'difficulty' | 'halving'): boolean { | ||||
|     this.mode = mode; | ||||
|     this.userSelectedMode = true; | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   @HostListener('pointerdown', ['$event']) | ||||
|   onPointerDown(event): void { | ||||
|     if (this.epochSvgElement?.nativeElement?.contains(event.target)) { | ||||
|  | ||||
| @ -32,6 +32,7 @@ | ||||
| } | ||||
| 
 | ||||
| .chart { | ||||
|   margin-top: 10px; | ||||
|   margin-bottom: 20px; | ||||
|   @media (max-width: 768px) { | ||||
|     margin-bottom: 10px; | ||||
|  | ||||
| @ -65,7 +65,9 @@ export class PoolComponent implements OnInit { | ||||
|             .pipe( | ||||
|               switchMap((data) => { | ||||
|                 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]; | ||||
|               }), | ||||
|               catchError(() => { | ||||
| @ -130,9 +132,9 @@ export class PoolComponent implements OnInit { | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   prepareChartOptions(data) { | ||||
|   prepareChartOptions(hashrate, share) { | ||||
|     let title: object; | ||||
|     if (data.length <= 1) { | ||||
|     if (hashrate.length <= 1) { | ||||
|       title = { | ||||
|         textStyle: { | ||||
|           color: 'grey', | ||||
| @ -177,26 +179,57 @@ export class PoolComponent implements OnInit { | ||||
|         }, | ||||
|         borderColor: '#000', | ||||
|         formatter: function (ticks: any[]) { | ||||
|           let hashratePowerOfTen: any = selectPowerOfTen(1); | ||||
|           let hashrate = ticks[0].data[1]; | ||||
|           let hashrateString = ''; | ||||
|           let dominanceString = ''; | ||||
| 
 | ||||
|           hashratePowerOfTen = selectPowerOfTen(ticks[0].data[1], 10); | ||||
|           hashrate = ticks[0].data[1] / hashratePowerOfTen.divider; | ||||
|           for (const tick of ticks) { | ||||
|             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 ` | ||||
|             <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) | ||||
|       }, | ||||
|       xAxis: data.length <= 1 ? undefined : { | ||||
|       xAxis: hashrate.length <= 1 ? undefined : { | ||||
|         type: 'time', | ||||
|         splitNumber: (this.isMobile()) ? 5 : 10, | ||||
|         axisLabel: { | ||||
|           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) => { | ||||
|             return value.min * 0.9; | ||||
| @ -214,21 +247,45 @@ export class PoolComponent implements OnInit { | ||||
|             show: false, | ||||
|           } | ||||
|         }, | ||||
|       ], | ||||
|       series: data.length <= 1 ? undefined : [ | ||||
|         { | ||||
|           zlevel: 0, | ||||
|           name: 'Hashrate', | ||||
|           type: 'value', | ||||
|           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, | ||||
|           symbol: 'none', | ||||
|           data: data, | ||||
|           data: hashrate, | ||||
|           type: 'line', | ||||
|           lineStyle: { | ||||
|             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', | ||||
|         realtime: true, | ||||
|         zoomLock: true, | ||||
|  | ||||
| @ -74,8 +74,7 @@ | ||||
|                   <td class="td-width" i18n="transaction.audit">Audit</td> | ||||
|                   <td *ngIf="pool" class="wrap-cell"> | ||||
|                     <ng-container *ngIf="auditStatus"> | ||||
|                       <span *ngIf="auditStatus.coinbase; else accelerated" 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> | ||||
|                       <span *ngIf="auditStatus.coinbase; else expected" class="badge badge-primary mr-1" i18n="tx-features.tag.coinbase|Coinbase">Coinbase</span> | ||||
|                       <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 #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> | ||||
| 
 | ||||
|     <ng-template #errorTemplate> | ||||
|       <div class="text-center"> | ||||
|         <h3>{{ error.error }}</h3> | ||||
|       </div> | ||||
|       <app-http-error [error]="error"> | ||||
|         <span i18n="transaction.error.loading-transaction-data">Error loading transaction data.</span> | ||||
|       </app-http-error> | ||||
|     </ng-template> | ||||
|   </ng-template> | ||||
| 
 | ||||
|  | ||||
| @ -66,9 +66,7 @@ | ||||
| </div> | ||||
| 
 | ||||
| <ng-template [ngIf]="error"> | ||||
|   <div class="text-center"> | ||||
|   <app-http-error [error]="error"> | ||||
|     <span i18n="error.general-loading-data">Error loading data.</span> | ||||
|     <br><br> | ||||
|     <i>{{ error.status }}: {{ error.error }}</i> | ||||
|   </div> | ||||
|   </app-http-error> | ||||
| </ng-template> | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| 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 { tap } from 'rxjs/operators'; | ||||
| import { catchError, tap } from 'rxjs/operators'; | ||||
| import { TransferState, makeStateKey } from '@angular/platform-browser'; | ||||
| import { isPlatformBrowser } from '@angular/common'; | ||||
| 
 | ||||
| @ -36,7 +36,8 @@ export class HttpCacheInterceptor implements HttpInterceptor { | ||||
|     } | ||||
| 
 | ||||
|     return next.handle(request) | ||||
|       .pipe(tap((event: HttpEvent<any>) => { | ||||
|       .pipe( | ||||
|         tap((event: HttpEvent<any>) => { | ||||
|           if (!this.isBrowser && event instanceof HttpResponse) { | ||||
|             let keyId = request.url.split('/').slice(3).join('/'); | ||||
|             const headers = {}; | ||||
| @ -45,6 +46,31 @@ export class HttpCacheInterceptor implements HttpInterceptor { | ||||
|             } | ||||
|             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 { WuBytesPipe } from './pipes/bytes-pipe/wubytes.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 { TimeComponent } from '../components/time/time.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 { CalculatorComponent } from '../components/calculator/calculator.component'; | ||||
| 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'; | ||||
| 
 | ||||
| @ -133,6 +135,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir | ||||
|     Decimal2HexPipe, | ||||
|     FeeRoundingPipe, | ||||
|     FiatCurrencyPipe, | ||||
|     HttpErrorPipe, | ||||
|     ColoredPriceDirective, | ||||
|     BrowserOnlyDirective, | ||||
|     ServerOnlyDirective, | ||||
| @ -208,6 +211,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir | ||||
|     AccelerationsListComponent, | ||||
|     AccelerationStatsComponent, | ||||
|     PendingStatsComponent, | ||||
|     HttpErrorComponent, | ||||
|   ], | ||||
|   imports: [ | ||||
|     CommonModule, | ||||
| @ -262,6 +266,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir | ||||
|     VbytesPipe, | ||||
|     WuBytesPipe, | ||||
|     FiatCurrencyPipe, | ||||
|     HttpErrorPipe, | ||||
|     CeilPipe, | ||||
|     ShortenStringPipe, | ||||
|     CapAddressPipe, | ||||
| @ -327,6 +332,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir | ||||
|     AccelerationsListComponent, | ||||
|     AccelerationStatsComponent, | ||||
|     PendingStatsComponent, | ||||
|     HttpErrorComponent, | ||||
| 
 | ||||
|     MempoolBlockOverviewComponent, | ||||
|     ClockchainComponent, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user