Merge pull request #912 from MiguelMedeiros/taproot-activation-fireworks

Add special blocks animation: fireworks.
This commit is contained in:
softsimon 2021-11-12 13:30:54 +04:00 committed by GitHub
commit 2dc930fad2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 277 additions and 42 deletions

View File

@ -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',
}
};

View File

@ -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">&nbsp;</a>
<div class="block-height">
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>

View File

@ -111,7 +111,7 @@
.flashing {
animation: opacityPulse 2s ease-out;
animation-iteration-count: infinite;
animation-iteration-count: infinite;
opacity: 1;
}
@ -119,4 +119,4 @@
0% {opacity: 0.7;}
50% {opacity: 1.0;}
100% {opacity: 0.7;}
}
}

View File

@ -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();

View File

@ -1,8 +1,8 @@
<ng-container *ngIf="(loadingBlocks$ | async) === false; else loadingBlocks">
<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">
<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">&nbsp;</a>
<div class="block-body">
<div class="fees">

View File

@ -18,7 +18,7 @@
.flashing {
animation: opacityPulse 2s ease-out;
animation-iteration-count: infinite;
animation-iteration-count: infinite;
opacity: 1;
}
@ -60,7 +60,7 @@
}
.bitcoin-block::after {
content: '';
content: '';
width: 125px;
height: 24px;
position:absolute;
@ -68,7 +68,7 @@
left: -20px;
background-color: #232838;
transform:skew(40deg);
transform-origin:top;
transform-origin:top;
}
.bitcoin-block::before {
@ -78,18 +78,18 @@
position: absolute;
top: -12px;
left: -20px;
background-color: #191c27;
background-color: #191c27;
transform: skewY(50deg);
transform-origin: top;
transform-origin: top;
}
.mempool-block.bitcoin-block::after {
background-color: #403834;
background-color: #403834;
}
.mempool-block.bitcoin-block::before {
background-color: #2d2825;
background-color: #2d2825;
}
.black-background {
@ -102,8 +102,8 @@
position: relative;
right: 75px;
top: 140px;
width: 0;
height: 0;
width: 0;
height: 0;
border-left: 35px solid transparent;
border-right: 35px solid transparent;
border-bottom: 35px solid #FFF;

View File

@ -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,26 +77,25 @@ 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) => {
block.index = this.blockIndex + i;
});
const stringifiedBlocks = JSON.stringify(blocks);
this.mempoolBlocksFull = JSON.parse(stringifiedBlocks);
this.mempoolBlocks = this.reduceMempoolBlocksToFitScreen(JSON.parse(stringifiedBlocks));
this.updateMempoolBlockStyles();
this.calculateTransactionPosition();
return this.mempoolBlocks;
})
);
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(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(
@ -118,7 +120,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
} else {
timeAvgMins += Math.abs(timeAvgDiff);
}
return timeAvgMins * 60 * 1000;
})
);

View File

@ -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>

View File

@ -7,5 +7,144 @@
}
#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%;
}
}

View File

@ -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);
}
});
}
}

View File

@ -26,6 +26,8 @@ export interface WebsocketResponse {
}
export interface MempoolBlock {
blink?: boolean;
height?: number;
blockSize: number;
blockVSize: number;
nTx: number;

View File

@ -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;
}