mirror of
https://github.com/mempool/mempool.git
synced 2025-01-18 21:32:55 +01:00
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: '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>
|
||||
|
@ -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;}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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"> </a>
|
||||
<div class="block-body">
|
||||
<div class="fees">
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
})
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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%;
|
||||
}
|
||||
}
|
||||
|
@ -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…
Reference in New Issue
Block a user