Merge pull request #912 from MiguelMedeiros/taproot-activation-fireworks
Add special blocks animation: fireworks.
This commit is contained in:
		
						commit
						2dc930fad2
					
				| @ -129,3 +129,12 @@ export const languages: Language[] = [ | |||||||
|    { code: 'vi', name: 'Tiếng Việt' },      // Vietnamese
 |    { code: 'vi', name: 'Tiếng Việt' },      // Vietnamese
 | ||||||
|    { code: 'zh', name: '中文' },            // Chinese
 |    { code: 'zh', name: '中文' },            // Chinese
 | ||||||
| ]; | ]; | ||||||
|  | 
 | ||||||
|  | export const specialBlocks = { | ||||||
|  |   '709632': { | ||||||
|  |     labelEvent: '🌱 Taproot activated!', | ||||||
|  |   }, | ||||||
|  |   '840000': { | ||||||
|  |     labelEvent: '🥳 Halving', | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| <div class="blocks-container blockchain-blocks-container" *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate"> | <div class="blocks-container blockchain-blocks-container" *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate"> | ||||||
|   <div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" > |   <div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" > | ||||||
|     <div class="text-center bitcoin-block mined-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]"> |     <div class="text-center bitcoin-block mined-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]" [class.blink-bg]="(specialBlocks[block.height] !== undefined)"> | ||||||
|       <a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" class="blockLink"> </a> |       <a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" class="blockLink"> </a> | ||||||
|       <div class="block-height"> |       <div class="block-height"> | ||||||
|         <a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a> |         <a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a> | ||||||
|  | |||||||
| @ -1,8 +1,9 @@ | |||||||
| import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, Input } from '@angular/core'; | import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; | ||||||
| import { Observable, Subscription } from 'rxjs'; | import { Observable, Subscription } from 'rxjs'; | ||||||
| import { Block } from 'src/app/interfaces/electrs.interface'; | import { Block } from 'src/app/interfaces/electrs.interface'; | ||||||
| import { StateService } from 'src/app/services/state.service'; | import { StateService } from 'src/app/services/state.service'; | ||||||
| import { Router } from '@angular/router'; | import { Router } from '@angular/router'; | ||||||
|  | import { specialBlocks } from 'src/app/app.constants'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-blockchain-blocks', |   selector: 'app-blockchain-blocks', | ||||||
| @ -11,7 +12,7 @@ import { Router } from '@angular/router'; | |||||||
|   changeDetection: ChangeDetectionStrategy.OnPush, |   changeDetection: ChangeDetectionStrategy.OnPush, | ||||||
| }) | }) | ||||||
| export class BlockchainBlocksComponent implements OnInit, OnDestroy { | export class BlockchainBlocksComponent implements OnInit, OnDestroy { | ||||||
|    |   specialBlocks = specialBlocks; | ||||||
|   network = ''; |   network = ''; | ||||||
|   blocks: Block[] = []; |   blocks: Block[] = []; | ||||||
|   emptyBlocks: Block[] = this.mountEmptyBlocks(); |   emptyBlocks: Block[] = this.mountEmptyBlocks(); | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
|   <div class="mempool-blocks-container" *ngIf="(timeAvg$ | async) as timeAvg;"> |   <div class="mempool-blocks-container" *ngIf="(timeAvg$ | async) as timeAvg;"> | ||||||
|     <div class="flashing"> |     <div class="flashing"> | ||||||
|       <ng-template ngFor let-projectedBlock [ngForOf]="mempoolBlocks$ | async" let-i="index" [ngForTrackBy]="trackByFn"> |       <ng-template ngFor let-projectedBlock [ngForOf]="mempoolBlocks$ | async" let-i="index" [ngForTrackBy]="trackByFn"> | ||||||
|         <div class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]"> |         <div class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]"  [class.blink-bg]="projectedBlock.blink"> | ||||||
|           <a [routerLink]="['/mempool-block/' | relativeUrl, i]" class="blockLink"> </a> |           <a [routerLink]="['/mempool-block/' | relativeUrl, i]" class="blockLink"> </a> | ||||||
|           <div class="block-body"> |           <div class="block-body"> | ||||||
|             <div class="fees"> |             <div class="fees"> | ||||||
|  | |||||||
| @ -5,6 +5,8 @@ import { StateService } from 'src/app/services/state.service'; | |||||||
| import { Router } from '@angular/router'; | import { Router } from '@angular/router'; | ||||||
| import { take, map, switchMap, share } from 'rxjs/operators'; | import { take, map, switchMap, share } from 'rxjs/operators'; | ||||||
| import { feeLevels, mempoolFeeColors } from 'src/app/app.constants'; | import { feeLevels, mempoolFeeColors } from 'src/app/app.constants'; | ||||||
|  | import { specialBlocks } from 'src/app/app.constants'; | ||||||
|  | import { Block } from 'src/app/interfaces/electrs.interface'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-mempool-blocks', |   selector: 'app-mempool-blocks', | ||||||
| @ -13,12 +15,13 @@ import { feeLevels, mempoolFeeColors } from 'src/app/app.constants'; | |||||||
|   changeDetection: ChangeDetectionStrategy.OnPush, |   changeDetection: ChangeDetectionStrategy.OnPush, | ||||||
| }) | }) | ||||||
| export class MempoolBlocksComponent implements OnInit, OnDestroy { | export class MempoolBlocksComponent implements OnInit, OnDestroy { | ||||||
| 
 |   specialBlocks = specialBlocks; | ||||||
|   mempoolBlocks: MempoolBlock[] = []; |   mempoolBlocks: MempoolBlock[] = []; | ||||||
|   mempoolEmptyBlocks: MempoolBlock[] = this.mountEmptyBlocks(); |   mempoolEmptyBlocks: MempoolBlock[] = this.mountEmptyBlocks(); | ||||||
|   mempoolBlocks$: Observable<MempoolBlock[]>; |   mempoolBlocks$: Observable<MempoolBlock[]>; | ||||||
|   timeAvg$: Observable<number>; |   timeAvg$: Observable<number>; | ||||||
|   loadingBlocks$: Observable<boolean>; |   loadingBlocks$: Observable<boolean>; | ||||||
|  |   blocksSubscription: Subscription; | ||||||
| 
 | 
 | ||||||
|   mempoolBlocksFull: MempoolBlock[] = []; |   mempoolBlocksFull: MempoolBlock[] = []; | ||||||
|   mempoolBlockStyles = []; |   mempoolBlockStyles = []; | ||||||
| @ -74,27 +77,26 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { | |||||||
|       fromEvent(window, 'resize') |       fromEvent(window, 'resize') | ||||||
|     ) |     ) | ||||||
|     .pipe( |     .pipe( | ||||||
|       switchMap(() => this.stateService.mempoolBlocks$), |         switchMap(() => combineLatest([ | ||||||
|       map((blocks) => { |           this.stateService.blocks$.pipe(map(([block]) => block)), | ||||||
|         if (!blocks.length) { |           this.stateService.mempoolBlocks$ | ||||||
|           return [{ index: 0, blockSize: 0, blockVSize: 0, feeRange: [0, 0], medianFee: 0, nTx: 0, totalFees: 0 }]; |         ])), | ||||||
|         } |         map(([lastBlock, mempoolBlocks]) => { | ||||||
|         return blocks; |           mempoolBlocks.forEach((block, i) => { | ||||||
|       }), |  | ||||||
|       map((blocks) => { |  | ||||||
|         blocks.forEach((block, i) => { |  | ||||||
|             block.index = this.blockIndex + i; |             block.index = this.blockIndex + i; | ||||||
|  |             block.height = lastBlock.height + i + 1; | ||||||
|  |             block.blink = specialBlocks[block.height] ? true : false; | ||||||
|           }); |           }); | ||||||
|         const stringifiedBlocks = JSON.stringify(blocks); |           const stringifiedBlocks = JSON.stringify(mempoolBlocks); | ||||||
|           this.mempoolBlocksFull = JSON.parse(stringifiedBlocks); |           this.mempoolBlocksFull = JSON.parse(stringifiedBlocks); | ||||||
|           this.mempoolBlocks = this.reduceMempoolBlocksToFitScreen(JSON.parse(stringifiedBlocks)); |           this.mempoolBlocks = this.reduceMempoolBlocksToFitScreen(JSON.parse(stringifiedBlocks)); | ||||||
|           this.updateMempoolBlockStyles(); |           this.updateMempoolBlockStyles(); | ||||||
|           this.calculateTransactionPosition(); |           this.calculateTransactionPosition(); | ||||||
|  | 
 | ||||||
|           return this.mempoolBlocks; |           return this.mempoolBlocks; | ||||||
|         }) |         }) | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     this.timeAvg$ = timer(0, 1000) |     this.timeAvg$ = timer(0, 1000) | ||||||
|       .pipe( |       .pipe( | ||||||
|         switchMap(() => combineLatest([ |         switchMap(() => combineLatest([ | ||||||
|  | |||||||
| @ -1,3 +1,10 @@ | |||||||
|  | <ng-container *ngIf="specialEvent"> | ||||||
|  |   <div class="pyro"> | ||||||
|  |     <div class="before"></div> | ||||||
|  |     <div class="after"></div> | ||||||
|  |   </div> | ||||||
|  |   <div class="warning-label">{{ eventName }}</div> | ||||||
|  | </ng-container> | ||||||
| <div id="blockchain-container" dir="ltr"> | <div id="blockchain-container" dir="ltr"> | ||||||
|   <app-blockchain></app-blockchain> |   <app-blockchain></app-blockchain> | ||||||
| </div> | </div> | ||||||
|  | |||||||
| @ -9,3 +9,142 @@ | |||||||
| #blockchain-container::-webkit-scrollbar { | #blockchain-container::-webkit-scrollbar { | ||||||
|   display: none; |   display: none; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .warning-label { | ||||||
|  |   position: absolute; | ||||||
|  |   width: 150px; | ||||||
|  |   text-align: center; | ||||||
|  |   font-weight: bold; | ||||||
|  |   font-size: 12px; | ||||||
|  |   padding: 6px 4px; | ||||||
|  |   border-radius: 4px; | ||||||
|  |   margin-top: -10px; | ||||||
|  |   margin-left: 10px; | ||||||
|  | } | ||||||
|  | // Fireworks | ||||||
|  | 
 | ||||||
|  | $particles: 50; | ||||||
|  | $width: 500; | ||||||
|  | $height: 500; | ||||||
|  | 
 | ||||||
|  | // Create the explosion... | ||||||
|  | $box-shadow: (); | ||||||
|  | $box-shadow2: (); | ||||||
|  | @for $i from 0 through $particles { | ||||||
|  |   $box-shadow: $box-shadow, | ||||||
|  |                random($width)-$width / 2 + px | ||||||
|  |                random($height)-$height / 1.2 + px | ||||||
|  |                hsl(random(360), 100, 50); | ||||||
|  |   $box-shadow2: $box-shadow2, 0 0 #fff | ||||||
|  | } | ||||||
|  | @mixin keyframes ($animationName) { | ||||||
|  |     @-webkit-keyframes #{$animationName} { | ||||||
|  |         @content; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @-moz-keyframes #{$animationName} { | ||||||
|  |         @content; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @-o-keyframes #{$animationName} { | ||||||
|  |         @content; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @-ms-keyframes #{$animationName} { | ||||||
|  |         @content; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @keyframes #{$animationName} { | ||||||
|  |         @content; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @mixin animation-delay ($settings) { | ||||||
|  |     -moz-animation-delay: $settings; | ||||||
|  |     -webkit-animation-delay: $settings; | ||||||
|  |     -o-animation-delay: $settings; | ||||||
|  |     -ms-animation-delay: $settings; | ||||||
|  |     animation-delay: $settings; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @mixin animation-duration ($settings) { | ||||||
|  |     -moz-animation-duration: $settings; | ||||||
|  |     -webkit-animation-duration: $settings; | ||||||
|  |     -o-animation-duration: $settings; | ||||||
|  |     -ms-animation-duration: $settings; | ||||||
|  |     animation-duration: $settings; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @mixin animation ($settings) { | ||||||
|  |     -moz-animation: $settings; | ||||||
|  |     -webkit-animation: $settings; | ||||||
|  |     -o-animation: $settings; | ||||||
|  |     -ms-animation: $settings; | ||||||
|  |     animation: $settings; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @mixin transform ($settings) { | ||||||
|  |     transform: $settings; | ||||||
|  |     -moz-transform: $settings; | ||||||
|  |     -webkit-transform: $settings; | ||||||
|  |     -o-transform: $settings; | ||||||
|  |     -ms-transform: $settings; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | body { | ||||||
|  |   margin:0; | ||||||
|  |   padding:0; | ||||||
|  |   background: #000; | ||||||
|  |   overflow: hidden; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .pyro > .before, .pyro > .after { | ||||||
|  |   z-index: 100; | ||||||
|  |   position: absolute; | ||||||
|  |   width: 5px; | ||||||
|  |   height: 5px; | ||||||
|  |   border-radius: 50%; | ||||||
|  |   box-shadow: $box-shadow2; | ||||||
|  |   @include animation((1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .pyro > .after { | ||||||
|  |   @include animation-delay((1.25s, 1.25s, 1.25s)); | ||||||
|  |   @include animation-duration((1.25s, 1.25s, 6.25s)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @include keyframes(bang) { | ||||||
|  |   to { | ||||||
|  |     box-shadow:$box-shadow; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @include keyframes(gravity)  { | ||||||
|  |   to { | ||||||
|  |     @include transform(translateY(200px)); | ||||||
|  |     opacity: 0; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @include keyframes(position) { | ||||||
|  |   0%, 19.9% { | ||||||
|  |     margin-top: 10%; | ||||||
|  |     margin-left: 40%; | ||||||
|  |   } | ||||||
|  |   20%, 39.9% { | ||||||
|  |     margin-top: 40%; | ||||||
|  |     margin-left: 30%; | ||||||
|  |   } | ||||||
|  |   40%, 59.9% { | ||||||
|  |     margin-top: 20%; | ||||||
|  |     margin-left: 70% | ||||||
|  |   } | ||||||
|  |   60%, 79.9% { | ||||||
|  |     margin-top: 30%; | ||||||
|  |     margin-left: 20%; | ||||||
|  |   } | ||||||
|  |   80%, 99.9% { | ||||||
|  |     margin-top: 30%; | ||||||
|  |     margin-left: 80%; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,10 +1,53 @@ | |||||||
| import { Component } from '@angular/core'; | import { Component, OnInit } from '@angular/core'; | ||||||
|  | import { WebsocketService } from 'src/app/services/websocket.service'; | ||||||
|  | import { StateService } from 'src/app/services/state.service'; | ||||||
|  | import { specialBlocks } from 'src/app/app.constants'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-start', |   selector: 'app-start', | ||||||
|   templateUrl: './start.component.html', |   templateUrl: './start.component.html', | ||||||
|   styleUrls: ['./start.component.scss'], |   styleUrls: ['./start.component.scss'], | ||||||
| }) | }) | ||||||
| export class StartComponent { | export class StartComponent implements OnInit { | ||||||
|   constructor() { } | 
 | ||||||
|  |   interval = 60; | ||||||
|  |   colors = ['#5E35B1', '#ffffff']; | ||||||
|  | 
 | ||||||
|  |   specialEvent = false; | ||||||
|  |   eventName = ''; | ||||||
|  |   optionsLeft = { | ||||||
|  |     particleCount: 2, | ||||||
|  |     angle: 70, | ||||||
|  |     spread: 50, | ||||||
|  |     origin: { x: 0 }, | ||||||
|  |     colors: this.colors, | ||||||
|  |   }; | ||||||
|  |   optionsRight = { | ||||||
|  |     particleCount: 2, | ||||||
|  |     angle: 110, | ||||||
|  |     spread: 50, | ||||||
|  |     origin: { x: 1 }, | ||||||
|  |     colors: this.colors, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   constructor( | ||||||
|  |     private websocketService: WebsocketService, | ||||||
|  |     private stateService: StateService, | ||||||
|  |   ) { } | ||||||
|  | 
 | ||||||
|  |   ngOnInit() { | ||||||
|  |     this.websocketService.want(['blocks', 'stats', 'mempool-blocks']); | ||||||
|  |     this.stateService.blocks$ | ||||||
|  |       .subscribe((blocks: any) => { | ||||||
|  |         const block = blocks[0]; | ||||||
|  |         if(specialBlocks[block.height]) { | ||||||
|  |           this.specialEvent = true; | ||||||
|  |           this.eventName = specialBlocks[block.height].labelEvent; | ||||||
|  |           setTimeout(() => { | ||||||
|  |             this.specialEvent = false; | ||||||
|  |           }, 60 * 60 * 1000); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -26,6 +26,8 @@ export interface WebsocketResponse { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface MempoolBlock { | export interface MempoolBlock { | ||||||
|  |   blink?: boolean; | ||||||
|  |   height?: number; | ||||||
|   blockSize: number; |   blockSize: number; | ||||||
|   blockVSize: number; |   blockVSize: number; | ||||||
|   nTx: number; |   nTx: number; | ||||||
|  | |||||||
| @ -941,3 +941,35 @@ th { | |||||||
|     width: 220px; |     width: 220px; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Blinking block | ||||||
|  | @keyframes shadowyBackground { | ||||||
|  |   0% { | ||||||
|  |     box-shadow: -10px -15px 75px rgba(#5E35B1, 1); | ||||||
|  |     transform: rotate(0deg) translateY(0px); | ||||||
|  |   } | ||||||
|  |   25% { | ||||||
|  |     transform: rotate(3deg) translateY(5px); | ||||||
|  |   } | ||||||
|  |   50% { | ||||||
|  |     box-shadow: -10px -15px 75px rgba(#5E35B1, .3); | ||||||
|  |     transform: rotate(0deg) translateY(0px); | ||||||
|  |   } | ||||||
|  |   75% { | ||||||
|  |     transform: rotate(-3deg) translateY(5px); | ||||||
|  |   } | ||||||
|  |   100% { | ||||||
|  |     box-shadow: -10px -15px 75px rgba(#5E35B1, 1); | ||||||
|  |     transform: rotate(0deg); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .blink-bg { | ||||||
|  |   color: #fff; | ||||||
|  |   background: repeating-linear-gradient(rgb(45, 51, 72), rgb(45, 51, 72) 0.163525%, rgb(16, 95, 176) 100%, rgb(147, 57, 244) 0.163525%) !important; | ||||||
|  |   animation: shadowyBackground 1s infinite; | ||||||
|  |   box-shadow: -10px -15px 75px rgba(#5E35B1, 1); | ||||||
|  |   transition: 100ms all ease-in; | ||||||
|  | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user