Merge pull request #912 from MiguelMedeiros/taproot-activation-fireworks
Add special blocks animation: fireworks.
This commit is contained in:
		
						commit
						716cf795b1
					
				@ -129,3 +129,12 @@ export const languages: Language[] = [
 | 
			
		||||
   { code: 'vi', name: 'Tiếng Việt' },      // Vietnamese
 | 
			
		||||
   { 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 *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>
 | 
			
		||||
      <div class="block-height">
 | 
			
		||||
        <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 { Block } from 'src/app/interfaces/electrs.interface';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
import { specialBlocks } from 'src/app/app.constants';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-blockchain-blocks',
 | 
			
		||||
@ -11,7 +12,7 @@ import { Router } from '@angular/router';
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class BlockchainBlocksComponent implements OnInit, OnDestroy {
 | 
			
		||||
  
 | 
			
		||||
  specialBlocks = specialBlocks;
 | 
			
		||||
  network = '';
 | 
			
		||||
  blocks: Block[] = [];
 | 
			
		||||
  emptyBlocks: Block[] = this.mountEmptyBlocks();
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
  <div class="mempool-blocks-container" *ngIf="(timeAvg$ | async) as timeAvg;">
 | 
			
		||||
    <div class="flashing">
 | 
			
		||||
      <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>
 | 
			
		||||
          <div class="block-body">
 | 
			
		||||
            <div class="fees">
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,8 @@ import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
import { take, map, switchMap, share } from 'rxjs/operators';
 | 
			
		||||
import { feeLevels, mempoolFeeColors } from 'src/app/app.constants';
 | 
			
		||||
import { specialBlocks } from 'src/app/app.constants';
 | 
			
		||||
import { Block } from 'src/app/interfaces/electrs.interface';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-mempool-blocks',
 | 
			
		||||
@ -13,12 +15,13 @@ import { feeLevels, mempoolFeeColors } from 'src/app/app.constants';
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class MempoolBlocksComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
  specialBlocks = specialBlocks;
 | 
			
		||||
  mempoolBlocks: MempoolBlock[] = [];
 | 
			
		||||
  mempoolEmptyBlocks: MempoolBlock[] = this.mountEmptyBlocks();
 | 
			
		||||
  mempoolBlocks$: Observable<MempoolBlock[]>;
 | 
			
		||||
  timeAvg$: Observable<number>;
 | 
			
		||||
  loadingBlocks$: Observable<boolean>;
 | 
			
		||||
  blocksSubscription: Subscription;
 | 
			
		||||
 | 
			
		||||
  mempoolBlocksFull: MempoolBlock[] = [];
 | 
			
		||||
  mempoolBlockStyles = [];
 | 
			
		||||
@ -74,27 +77,26 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
 | 
			
		||||
      fromEvent(window, 'resize')
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(
 | 
			
		||||
      switchMap(() => this.stateService.mempoolBlocks$),
 | 
			
		||||
      map((blocks) => {
 | 
			
		||||
        if (!blocks.length) {
 | 
			
		||||
          return [{ index: 0, blockSize: 0, blockVSize: 0, feeRange: [0, 0], medianFee: 0, nTx: 0, totalFees: 0 }];
 | 
			
		||||
        }
 | 
			
		||||
        return blocks;
 | 
			
		||||
      }),
 | 
			
		||||
      map((blocks) => {
 | 
			
		||||
        blocks.forEach((block, i) => {
 | 
			
		||||
        switchMap(() => combineLatest([
 | 
			
		||||
          this.stateService.blocks$.pipe(map(([block]) => block)),
 | 
			
		||||
          this.stateService.mempoolBlocks$
 | 
			
		||||
        ])),
 | 
			
		||||
        map(([lastBlock, mempoolBlocks]) => {
 | 
			
		||||
          mempoolBlocks.forEach((block, 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.mempoolBlocks = this.reduceMempoolBlocksToFitScreen(JSON.parse(stringifiedBlocks));
 | 
			
		||||
          this.updateMempoolBlockStyles();
 | 
			
		||||
          this.calculateTransactionPosition();
 | 
			
		||||
 | 
			
		||||
          return this.mempoolBlocks;
 | 
			
		||||
        })
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    this.timeAvg$ = timer(0, 1000)
 | 
			
		||||
      .pipe(
 | 
			
		||||
        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">
 | 
			
		||||
  <app-blockchain></app-blockchain>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -9,3 +9,142 @@
 | 
			
		||||
#blockchain-container::-webkit-scrollbar {
 | 
			
		||||
  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({
 | 
			
		||||
  selector: 'app-start',
 | 
			
		||||
  templateUrl: './start.component.html',
 | 
			
		||||
  styleUrls: ['./start.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class StartComponent {
 | 
			
		||||
  constructor() { }
 | 
			
		||||
export class StartComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  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 {
 | 
			
		||||
  blink?: boolean;
 | 
			
		||||
  height?: number;
 | 
			
		||||
  blockSize: number;
 | 
			
		||||
  blockVSize: number;
 | 
			
		||||
  nTx: number;
 | 
			
		||||
 | 
			
		||||
@ -941,3 +941,35 @@ th {
 | 
			
		||||
    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