responsive clock, fix blockchain

This commit is contained in:
Mononaut 2023-04-19 03:34:13 +09:00
parent 61531171c9
commit f879a34021
No known key found for this signature in database
GPG Key ID: A3F058E41374C04E
21 changed files with 412 additions and 396 deletions

View File

@ -36,7 +36,6 @@ export default class BlockScene {
this.gridSize = this.width / this.gridWidth; this.gridSize = this.width / this.gridWidth;
this.unitPadding = Math.max(1, Math.floor(this.gridSize / 2.5)); this.unitPadding = Math.max(1, Math.floor(this.gridSize / 2.5));
this.unitWidth = this.gridSize - (this.unitPadding); this.unitWidth = this.gridSize - (this.unitPadding);
console.log(this.gridSize, this.unitPadding, this.unitWidth);
this.dirty = true; this.dirty = true;
if (this.initialised && this.scene) { if (this.initialised && this.scene) {

View File

@ -1,21 +1,21 @@
<div class="blocks-container blockchain-blocks-container" [class.time-ltr]="timeLtr" [class.tiny]="tiny" <div class="blocks-container blockchain-blocks-container" [class.time-ltr]="timeLtr" [class.minimal]="minimal"
[style.left]="static ? (offset || 0) + 'px' : null" [style.left]="static ? (offset || 0) + 'px' : null" [style.--block-size]="blockWidth+'px'"
*ngIf="static || (loadingBlocks$ | async) === false; else loadingBlocksTemplate"> *ngIf="static || (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">
<ng-container *ngIf="connected && block && !block.loading && !block.placeholder; else placeholderBlock"> <ng-container *ngIf="connected && block && !block.loading && !block.placeholder; else placeholderBlock">
<div [attr.data-cy]="'bitcoin-block-offset-' + offset + '-index-' + i" <div [attr.data-cy]="'bitcoin-block-offset-' + offset + '-index-' + i"
class="text-center bitcoin-block mined-block blockchain-blocks-offset-{{ offset }}-index-{{ i }}" class="text-center bitcoin-block mined-block blockchain-blocks-offset-{{ offset }}-index-{{ i }}"
[class.offscreen]="tiny && i >= 6" [class.offscreen]="!static && count && i >= count"
id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]"
[class.blink-bg]="isSpecial(block.height)"> [class.blink-bg]="isSpecial(block.height)">
<a draggable="false" [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" <a draggable="false" [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }"
class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}">&nbsp;</a> class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}">&nbsp;</a>
<div *ngIf="!tiny" [attr.data-cy]="'bitcoin-block-' + i + '-height'" class="block-height"> <div *ngIf="!minimal" [attr.data-cy]="'bitcoin-block-' + i + '-height'" class="block-height">
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height <a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height
}}</a> }}</a>
</div> </div>
<div class="block-body"> <div class="block-body">
<ng-container *ngIf="!tiny"> <ng-container *ngIf="!minimal">
<div *ngIf="block?.extras; else emptyfees" [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees"> <div *ngIf="block?.extras; else emptyfees" [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees">
~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container ~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container
i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container> i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
@ -82,11 +82,11 @@
</div> </div>
<ng-template #loadingBlocksTemplate> <ng-template #loadingBlocksTemplate>
<div class="blocks-container" [class.time-ltr]="timeLtr"> <div class="blocks-container" [class.time-ltr]="timeLtr" [style.--block-size]="blockWidth+'px'">
<div class="flashing"> <div class="flashing">
<div *ngFor="let block of emptyBlocks; let i = index; trackBy: trackByBlocksFn"> <div *ngFor="let block of emptyBlocks; let i = index; trackBy: trackByBlocksFn">
<div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" <div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}"
[ngStyle]="emptyBlockStyles[i]"></div> [ngStyle]="emptyBlockStyles[i]" [class.offscreen]="!static && count && i >= count"></div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
.bitcoin-block { .bitcoin-block {
width: 125px; width: var(--block-size);
height: 125px; height: var(--block-size);
} }
.blockLink { .blockLink {
@ -39,9 +39,11 @@
} }
.blocks-container { .blocks-container {
--block-size: 125px;
--block-offset: calc(0.32 * var(--block-size));
position: absolute; position: absolute;
top: 0px; top: 0px;
left: 40px; left: var(--block-offset);
} }
.block-body { .block-body {
@ -81,11 +83,11 @@
.bitcoin-block::after { .bitcoin-block::after {
content: ''; content: '';
width: 125px; width: var(--block-size);
height: 24px; height: calc(0.192 * var(--block-size));
position:absolute; position:absolute;
top: -24px; top: calc(-0.192 * var(--block-size));
left: -20px; left: calc(-0.16 * var(--block-size));
background-color: #232838; background-color: #232838;
transform:skew(40deg); transform:skew(40deg);
transform-origin:top; transform-origin:top;
@ -93,11 +95,11 @@
.bitcoin-block::before { .bitcoin-block::before {
content: ''; content: '';
width: 20px; width: calc(0.16 * var(--block-size));
height: 125px; height: var(--block-size);
position: absolute; position: absolute;
top: -12px; top: calc(-0.096 * var(--block-size));
left: -20px; left: calc(-0.16 * var(--block-size));
background-color: #191c27; background-color: #191c27;
transform: skewY(50deg); transform: skewY(50deg);

View File

@ -24,7 +24,8 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
@Input() count: number = 8; // number of blocks in this chunk (dynamic blocks only) @Input() count: number = 8; // number of blocks in this chunk (dynamic blocks only)
@Input() loadingTip: boolean = false; @Input() loadingTip: boolean = false;
@Input() connected: boolean = true; @Input() connected: boolean = true;
@Input() tiny: boolean = false; @Input() minimal: boolean = false;
@Input() blockWidth: number = 125;
specialBlocks = specialBlocks; specialBlocks = specialBlocks;
network = ''; network = '';
@ -52,6 +53,10 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
timeLtrSubscription: Subscription; timeLtrSubscription: Subscription;
timeLtr: boolean; timeLtr: boolean;
blockOffset: number = 155;
dividerBlockOffset: number = 205;
blockPadding: number = 30;
gradientColors = { gradientColors = {
'': ['#9339f4', '#105fb0'], '': ['#9339f4', '#105fb0'],
bisq: ['#9339f4', '#105fb0'], bisq: ['#9339f4', '#105fb0'],
@ -119,7 +124,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
this.blockStyles = []; this.blockStyles = [];
if (this.blocksFilled && block.height > this.chainTip) { if (this.blocksFilled && block.height > this.chainTip) {
this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i, i ? -155 : -205))); this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i, i ? -this.blockOffset : -this.dividerBlockOffset)));
setTimeout(() => { setTimeout(() => {
this.blockStyles = []; this.blockStyles = [];
this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i))); this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i)));
@ -160,6 +165,13 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (changes.blockWidth && this.blockWidth) {
this.blockPadding = 0.24 * this.blockWidth;
this.blockOffset = this.blockWidth + this.blockPadding;
this.dividerBlockOffset = this.blockOffset + (0.4 * this.blockWidth);
this.blockStyles = [];
this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i)));
}
if (this.static) { if (this.static) {
const animateSlide = changes.height && (changes.height.currentValue === changes.height.previousValue + 1); const animateSlide = changes.height && (changes.height.currentValue === changes.height.previousValue + 1);
this.updateStaticBlocks(animateSlide); this.updateStaticBlocks(animateSlide);
@ -192,14 +204,14 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
} }
this.arrowVisible = true; this.arrowVisible = true;
if (newBlockFromLeft) { if (newBlockFromLeft) {
this.arrowLeftPx = blockindex * 155 + 30 - 205; this.arrowLeftPx = blockindex * this.blockOffset + this.blockPadding - this.dividerBlockOffset;
setTimeout(() => { setTimeout(() => {
this.arrowTransition = '2s'; this.arrowTransition = '2s';
this.arrowLeftPx = blockindex * 155 + 30; this.arrowLeftPx = blockindex * this.blockOffset + this.blockPadding;
this.cd.markForCheck(); this.cd.markForCheck();
}, 50); }, 50);
} else { } else {
this.arrowLeftPx = blockindex * 155 + 30; this.arrowLeftPx = blockindex * this.blockOffset + this.blockPadding;
if (!animate) { if (!animate) {
setTimeout(() => { setTimeout(() => {
this.arrowTransition = '2s'; this.arrowTransition = '2s';
@ -246,7 +258,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
} }
this.blocks = this.blocks.slice(0, this.count); this.blocks = this.blocks.slice(0, this.count);
this.blockStyles = []; this.blockStyles = [];
this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i, animateSlide ? -155 : 0))); this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i, animateSlide ? -this.blockOffset : 0)));
this.cd.markForCheck(); this.cd.markForCheck();
if (animateSlide) { if (animateSlide) {
// animate blocks slide right // animate blocks slide right
@ -288,7 +300,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
} }
return { return {
left: addLeft + 155 * index + 'px', left: addLeft + this.blockOffset * index + 'px',
background: `repeating-linear-gradient( background: `repeating-linear-gradient(
#2d3348, #2d3348,
#2d3348 ${greenBackgroundHeight}%, #2d3348 ${greenBackgroundHeight}%,
@ -310,7 +322,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
const addLeft = animateEnterFrom || 0; const addLeft = animateEnterFrom || 0;
return { return {
left: addLeft + (155 * index) + 'px', left: addLeft + (this.blockOffset * index) + 'px',
background: "#2d3348", background: "#2d3348",
}; };
} }
@ -318,7 +330,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
getStyleForPlaceholderBlock(index: number, animateEnterFrom: number = 0) { getStyleForPlaceholderBlock(index: number, animateEnterFrom: number = 0) {
const addLeft = animateEnterFrom || 0; const addLeft = animateEnterFrom || 0;
return { return {
left: addLeft + (155 * index) + 'px', left: addLeft + (this.blockOffset * index) + 'px',
}; };
} }
@ -326,7 +338,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
const addLeft = animateEnterFrom || 0; const addLeft = animateEnterFrom || 0;
return { return {
left: addLeft + 155 * this.emptyBlocks.indexOf(block) + 'px', left: addLeft + this.blockOffset * this.emptyBlocks.indexOf(block) + 'px',
background: "#2d3348", background: "#2d3348",
}; };
} }

View File

@ -1,9 +1,3 @@
<div class="clock-wrapper" [style]="wrapperStyle">
<div class="clockchain-bar">
<div class="clockchain" [style]="chainStyle">
<app-clockchain></app-clockchain>
</div>
</div>
<div class="clock-face" [style]="faceStyle"> <div class="clock-face" [style]="faceStyle">
<ng-content></ng-content> <ng-content></ng-content>
<svg <svg
@ -91,4 +85,3 @@
<path id="gnomon" style="opacity:1;fill:#80C2E1;fill-opacity:1;stroke:none;stroke-width:3.73798;stroke-opacity:1" d="M 46.463002,316.72751 33.743954,300.31177 65.383738,288.93232 Z" /> <path id="gnomon" style="opacity:1;fill:#80C2E1;fill-opacity:1;stroke:none;stroke-width:3.73798;stroke-opacity:1" d="M 46.463002,316.72751 33.743954,300.31177 65.383738,288.93232 Z" />
</svg> </svg>
</div> </div>
</div>

View File

@ -1,27 +1,3 @@
.clock-wrapper {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
.clockchain-bar, .clock-face {
flex-shrink: 0;
flex-grow: 0;
}
.clockchain-bar {
position: relative;
height: 15.625%;
// background: #1d1f31;
// box-shadow: 0 0 15px #000;
}
.clock-face { .clock-face {
position: relative; position: relative;
height: 84.375%; height: 84.375%;
@ -42,4 +18,3 @@
} }
} }
} }
}

View File

@ -1,36 +1,17 @@
import { Component, HostListener, OnInit } from '@angular/core'; import { Component, Input, OnChanges } from '@angular/core';
@Component({ @Component({
selector: 'app-clock-face', selector: 'app-clock-face',
templateUrl: './clock-face.component.html', templateUrl: './clock-face.component.html',
styleUrls: ['./clock-face.component.scss'], styleUrls: ['./clock-face.component.scss'],
}) })
export class ClockFaceComponent implements OnInit { export class ClockFaceComponent implements OnChanges {
size: number; @Input() size: number = 300;
wrapperStyle;
chainStyle;
faceStyle; faceStyle;
showDial: boolean = false;
constructor() {} constructor() {}
ngOnInit(): void { ngOnChanges(): void {
// initialize stuff
this.resizeCanvas();
}
@HostListener('window:resize', ['$event'])
resizeCanvas(): void {
this.size = Math.min(window.innerWidth, 0.78125 * window.innerHeight);
this.wrapperStyle = {
'--clock-width': `${this.size}px`
};
const scaleFactor = window.innerWidth / 1390;
this.chainStyle = {
transform: `translate(2vw, 0.5vw) scale(${scaleFactor})`,
transformOrigin: 'top left',
};
this.faceStyle = { this.faceStyle = {
width: `${this.size}px`, width: `${this.size}px`,
height: `${this.size}px`, height: `${this.size}px`,

View File

@ -1,17 +1 @@
<app-clock-face> <app-clock mode="block"></app-clock>
<div class="block-wrapper">
<ng-container *ngIf="block && block.height >= 0">
<div class="block-cube">
<div class="side top"></div>
<div class="side bottom"></div>
<div class="side right" [style]="blockStyle"></div>
<div class="side left" [style]="blockStyle"></div>
<div class="side front" [style]="blockStyle"></div>
<div class="side back" [style]="blockStyle"></div>
</div>
<div class="title-wrapper">
<h1 class="block-height">{{ block.height }}</h1>
</div>
</ng-container>
</div>
</app-clock-face>

View File

@ -1,57 +1,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { Component } from '@angular/core';
import { Subscription } from 'rxjs';
import { StateService } from '../../services/state.service';
import { BlockExtended } from '../../interfaces/node-api.interface';
import { WebsocketService } from '../../services/websocket.service';
@Component({ @Component({
selector: 'app-clock-a', selector: 'app-clock-a',
templateUrl: './clock-a.component.html', templateUrl: './clock-a.component.html',
styleUrls: ['./clock.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class ClockAComponent implements OnInit { export class ClockAComponent {}
blocksSubscription: Subscription;
block: BlockExtended;
blockStyle;
gradientColors = {
'': ['#9339f4', '#105fb0'],
bisq: ['#9339f4', '#105fb0'],
liquid: ['#116761', '#183550'],
'liquidtestnet': ['#494a4a', '#272e46'],
testnet: ['#1d486f', '#183550'],
signet: ['#6f1d5d', '#471850'],
};
constructor(
public stateService: StateService,
private websocketService: WebsocketService,
private cd: ChangeDetectorRef,
) {}
ngOnInit(): void {
this.websocketService.want(['blocks']);
this.blocksSubscription = this.stateService.blocks$
.subscribe(([block]) => {
if (block) {
this.block = block;
this.blockStyle = this.getStyleForBlock(this.block);
this.cd.markForCheck();
}
});
}
getStyleForBlock(block: BlockExtended) {
const greenBackgroundHeight = 100 - (block.weight / this.stateService.env.BLOCK_WEIGHT_UNITS) * 100;
return {
background: `repeating-linear-gradient(
#2d3348,
#2d3348 ${greenBackgroundHeight}%,
${this.gradientColors[''][0]} ${Math.max(greenBackgroundHeight, 0)}%,
${this.gradientColors[''][1]} 100%
)`,
};
}
}

View File

@ -1,13 +1 @@
<app-clock-face> <app-clock mode="mempool"></app-clock>
<div class="block-wrapper">
<ng-container *ngIf="block && block.height >= 0">
<div class="block-sizer" [style]="blockSizerStyle">
<app-mempool-block-overview [index]="0"></app-mempool-block-overview>
</div>
<div class="fader"></div>
<div class="title-wrapper">
<h1 class="block-height">{{ block.height }}</h1>
</div>
</ng-container>
</div>
</app-clock-face>

View File

@ -1,57 +1,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnInit } from '@angular/core'; import { Component } from '@angular/core';
import { Subscription } from 'rxjs';
import { StateService } from '../../services/state.service';
import { BlockExtended } from '../../interfaces/node-api.interface';
import { WebsocketService } from '../../services/websocket.service';
@Component({ @Component({
selector: 'app-clock-b', selector: 'app-clock-b',
templateUrl: './clock-b.component.html', templateUrl: './clock-b.component.html',
styleUrls: ['./clock.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class ClockBComponent implements OnInit { export class ClockBComponent {}
blocksSubscription: Subscription;
block: BlockExtended;
blockSizerStyle;
gradientColors = {
'': ['#9339f4', '#105fb0'],
bisq: ['#9339f4', '#105fb0'],
liquid: ['#116761', '#183550'],
'liquidtestnet': ['#494a4a', '#272e46'],
testnet: ['#1d486f', '#183550'],
signet: ['#6f1d5d', '#471850'],
};
constructor(
public stateService: StateService,
private websocketService: WebsocketService,
private cd: ChangeDetectorRef,
) {}
ngOnInit(): void {
this.resizeCanvas();
this.websocketService.want(['blocks']);
this.blocksSubscription = this.stateService.blocks$
.subscribe(([block]) => {
if (block) {
this.block = block;
this.cd.markForCheck();
}
});
}
@HostListener('window:resize', ['$event'])
resizeCanvas(): void {
const clockSize = Math.min(window.innerWidth, 0.78125 * window.innerHeight);
const size = Math.ceil(clockSize / 75) * 75;
const margin = (clockSize - size) / 2;
this.blockSizerStyle = {
transform: `translate(${margin}px, ${margin}px)`,
width: `${size}px`,
height: `${size}px`,
};
this.cd.markForCheck();
}
}

View File

@ -0,0 +1,34 @@
<div class="clock-wrapper" [style]="wrapperStyle">
<div class="clockchain-bar" [style.height]="chainHeight + 'px'">
<div class="clockchain">
<app-clockchain [width]="chainWidth" [height]="chainHeight"></app-clockchain>
</div>
</div>
<div class="clock-face">
<app-clock-face [size]="clockSize">
<div class="block-wrapper">
<ng-container *ngIf="block && block.height >= 0">
<ng-container *ngIf="mode === 'block'; else mempoolMode;">
<div class="block-cube">
<div class="side top"></div>
<div class="side bottom"></div>
<div class="side right" [style]="blockStyle"></div>
<div class="side left" [style]="blockStyle"></div>
<div class="side front" [style]="blockStyle"></div>
<div class="side back" [style]="blockStyle"></div>
</div>
</ng-container>
<ng-template #mempoolMode>
<div class="block-sizer" [style]="blockSizerStyle">
<app-mempool-block-overview [index]="0"></app-mempool-block-overview>
</div>
</ng-template>
<div class="fader"></div>
<div class="title-wrapper">
<h1 class="block-height">{{ block.height }}</h1>
</div>
</ng-container>
</div>
</app-clock-face>
</div>
</div>

View File

@ -1,3 +1,44 @@
.clock-wrapper {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
--clock-width: 300px;
.clockchain-bar, .clock-face {
flex-shrink: 0;
flex-grow: 0;
}
.clockchain-bar {
position: relative;
width: 100%;
height: 15.625%;
z-index: 2;
overflow: hidden;
// background: #1d1f31;
// box-shadow: 0 0 15px #000;
}
.clock-face {
position: relative;
height: 84.375%;
margin: auto;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
}
}
.title-wrapper { .title-wrapper {
position: absolute; position: absolute;
left: 0; left: 0;
@ -101,8 +142,6 @@
} }
} }
@keyframes block-spin { @keyframes block-spin {
0% {transform: translate(-50%, -50%) rotateX(-20deg) rotateY(0deg);} 0% {transform: translate(-50%, -50%) rotateX(-20deg) rotateY(0deg);}
100% {transform: translate(-50%, -50%) rotateX(-20deg) rotateY(-360deg);} 100% {transform: translate(-50%, -50%) rotateX(-20deg) rotateY(-360deg);}

View File

@ -0,0 +1,82 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, Input, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { StateService } from '../../services/state.service';
import { BlockExtended } from '../../interfaces/node-api.interface';
import { WebsocketService } from '../../services/websocket.service';
@Component({
selector: 'app-clock',
templateUrl: './clock.component.html',
styleUrls: ['./clock.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ClockComponent implements OnInit {
@Input() mode: string = 'block';
blocksSubscription: Subscription;
block: BlockExtended;
clockSize: number = 300;
chainWidth: number = 384;
chainHeight: number = 60;
blockStyle;
blockSizerStyle;
wrapperStyle;
gradientColors = {
'': ['#9339f4', '#105fb0'],
bisq: ['#9339f4', '#105fb0'],
liquid: ['#116761', '#183550'],
'liquidtestnet': ['#494a4a', '#272e46'],
testnet: ['#1d486f', '#183550'],
signet: ['#6f1d5d', '#471850'],
};
constructor(
public stateService: StateService,
private websocketService: WebsocketService,
private cd: ChangeDetectorRef,
) {}
ngOnInit(): void {
this.resizeCanvas();
this.websocketService.want(['blocks']);
this.blocksSubscription = this.stateService.blocks$
.subscribe(([block]) => {
if (block) {
this.block = block;
this.blockStyle = this.getStyleForBlock(this.block);
this.cd.markForCheck();
}
});
}
getStyleForBlock(block: BlockExtended) {
const greenBackgroundHeight = 100 - (block.weight / this.stateService.env.BLOCK_WEIGHT_UNITS) * 100;
return {
background: `repeating-linear-gradient(
#2d3348,
#2d3348 ${greenBackgroundHeight}%,
${this.gradientColors[''][0]} ${Math.max(greenBackgroundHeight, 0)}%,
${this.gradientColors[''][1]} 100%
)`,
};
}
@HostListener('window:resize', ['$event'])
resizeCanvas(): void {
this.chainWidth = window.innerWidth;
this.chainHeight = Math.max(60, window.innerHeight / 8);
this.clockSize = Math.min(500, window.innerWidth, window.innerHeight - (1.4 * this.chainHeight));
const size = Math.ceil(this.clockSize / 75) * 75;
const margin = (this.clockSize - size) / 2;
this.blockSizerStyle = {
transform: `translate(${margin}px, ${margin}px)`,
width: `${size}px`,
height: `${size}px`,
};
this.wrapperStyle = {
'--clock-width': `${this.clockSize}px`
};
this.cd.markForCheck();
}
}

View File

@ -1,11 +1,25 @@
<div class="text-center" class="blockchain-wrapper" [class.time-ltr]="timeLtr" [class.ltr-transition]="ltrTransitionEnabled" #container> <div class="text-center" class="blockchain-wrapper" [class.time-ltr]="timeLtr" [class.ltr-transition]="ltrTransitionEnabled" #container>
<div class="position-container" [ngClass]="network ? network : ''"> <div class="position-container" [ngClass]="network ? network : ''" [style.top]="(height / 3) + 'px'">
<span> <span>
<div class="blocks-wrapper"> <div class="blocks-wrapper">
<app-mempool-blocks [tiny]="true" [count]="3"></app-mempool-blocks> <app-mempool-blocks [minimal]="true" [count]="mempoolBlocks" [blockWidth]="blockWidth"></app-mempool-blocks>
<app-blockchain-blocks [tiny]="true"></app-blockchain-blocks> <app-blockchain-blocks [minimal]="true" [count]="blockchainBlocks" [blockWidth]="blockWidth"></app-blockchain-blocks>
</div>
<div class="divider" [style.top]="-(height / 6) + 'px'">
<svg
viewBox="0 0 2 175"
[style.width]="'2px'"
[style.height]="(5 * height / 6) + 'px'"
>
<line
class="divider-line"
x0="0"
x1="0"
y0="0"
y1="175px"
></line>
</svg>
</div> </div>
<div class="divider"></div>
</span> </span>
</div> </div>
</div> </div>

View File

@ -1,15 +1,17 @@
.divider { .divider {
width: 4px;
height: 180px;
left: 0;
top: -40px;
position: absolute; position: absolute;
margin-bottom: 120px; left: -1px;
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3cline x1='0' y1='0' x2='0' y2='100%' stroke='white' stroke-width='8' stroke-dasharray='18%2c32' stroke-dashoffset='-5' stroke-linecap='square'/%3e%3c/svg%3e"); top: 0;
.divider-line {
stroke: white;
stroke-width: 4px;
stroke-linecap: butt;
stroke-dasharray: 25px 25px;
}
} }
.blockchain-wrapper { .blockchain-wrapper {
height: 250px; height: 100%;
-webkit-user-select: none; /* Safari */ -webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */ -moz-user-select: none; /* Firefox */
@ -20,37 +22,10 @@
.position-container { .position-container {
position: absolute; position: absolute;
left: 0; left: 0;
top: 75px; top: 0;
transform: translateX(50vw); transform: translateX(50vw);
} }
.position-container.liquid, .position-container.liquidtestnet {
transform: translateX(420px);
}
.blockchain-wrapper {
.position-container {
transform: translateX(95vw);
}
.position-container.liquid, .position-container.liquidtestnet {
transform: translateX(50vw);
}
.position-container.loading {
transform: translateX(50vw);
}
}
.blockchain-wrapper.time-ltr {
.position-container {
transform: translateX(5vw);
}
.position-container.liquid, .position-container.liquidtestnet {
transform: translateX(50vw);
}
.position-container.loading {
transform: translateX(50vw);
}
}
.black-background { .black-background {
background-color: #11131f; background-color: #11131f;
z-index: 100; z-index: 100;

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input } from '@angular/core'; import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, OnChanges, ChangeDetectorRef } from '@angular/core';
import { firstValueFrom, Subscription } from 'rxjs'; import { firstValueFrom, Subscription } from 'rxjs';
import { StateService } from '../../services/state.service'; import { StateService } from '../../services/state.service';
@ -8,7 +8,15 @@ import { StateService } from '../../services/state.service';
styleUrls: ['./clockchain.component.scss'], styleUrls: ['./clockchain.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class ClockchainComponent implements OnInit, OnDestroy { export class ClockchainComponent implements OnInit, OnChanges, OnDestroy {
@Input() width: number = 300;
@Input() height: number = 60;
mempoolBlocks: number = 3;
blockchainBlocks: number = 6;
blockWidth: number = 50;
dividerStyle;
network: string; network: string;
timeLtrSubscription: Subscription; timeLtrSubscription: Subscription;
timeLtr: boolean = this.stateService.timeLtr.value; timeLtr: boolean = this.stateService.timeLtr.value;
@ -19,9 +27,12 @@ export class ClockchainComponent implements OnInit, OnDestroy {
constructor( constructor(
public stateService: StateService, public stateService: StateService,
private cd: ChangeDetectorRef,
) {} ) {}
ngOnInit() { ngOnInit() {
this.ngOnChanges();
this.network = this.stateService.network; this.network = this.stateService.network;
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => { this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
this.timeLtr = !!ltr; this.timeLtr = !!ltr;
@ -34,6 +45,17 @@ export class ClockchainComponent implements OnInit, OnDestroy {
}); });
} }
ngOnChanges() {
this.blockWidth = Math.floor(7 * this.height / 12);
this.mempoolBlocks = Math.floor(((this.width / 2) - (this.blockWidth * 0.32)) / (1.24 * this.blockWidth));
this.blockchainBlocks = this.mempoolBlocks;
this.dividerStyle = {
width: '2px',
height: `${this.height}px`,
};
this.cd.markForCheck();
}
ngOnDestroy() { ngOnDestroy() {
this.timeLtrSubscription.unsubscribe(); this.timeLtrSubscription.unsubscribe();
this.connectionStateSubscription.unsubscribe(); this.connectionStateSubscription.unsubscribe();

View File

@ -1,12 +1,12 @@
<ng-container *ngIf="(loadingBlocks$ | async) === false; else loadingBlocks" [class.tiny]="tiny"> <ng-container *ngIf="(loadingBlocks$ | async) === false; else loadingBlocks" [class.minimal]="minimal">
<div class="mempool-blocks-container" [class.time-ltr]="timeLtr" *ngIf="(difficultyAdjustments$ | async) as da;"> <div class="mempool-blocks-container" [class.time-ltr]="timeLtr" [style.--block-size]="blockWidth+'px'" *ngIf="(difficultyAdjustments$ | async) as da;">
<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 @blockEntryTrigger [@.disabled]="i > 0 || !animateEntry" [attr.data-cy]="'mempool-block-' + i" class="bitcoin-block text-center mempool-block" [class.last-block]="tiny && i >= count - 1" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]" [class.blink-bg]="projectedBlock.blink"> <div @blockEntryTrigger [@.disabled]="i > 0 || !animateEntry" [attr.data-cy]="'mempool-block-' + i" class="bitcoin-block text-center mempool-block" [class.hide-block]="count && i >= count" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]" [class.blink-bg]="projectedBlock.blink">
<a draggable="false" [routerLink]="['/mempool-block/' | relativeUrl, i]" <a draggable="false" [routerLink]="['/mempool-block/' | relativeUrl, i]"
class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}">&nbsp;</a> class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}">&nbsp;</a>
<div class="block-body"> <div class="block-body">
<ng-container *ngIf="!tiny"> <ng-container *ngIf="!minimal">
<div [attr.data-cy]="'mempool-block-' + i + '-fees'" class="fees"> <div [attr.data-cy]="'mempool-block-' + i + '-fees'" class="fees">
~{{ projectedBlock.medianFee | number:feeRounding }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span> ~{{ projectedBlock.medianFee | number:feeRounding }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
</div> </div>
@ -73,10 +73,10 @@
</ng-container> </ng-container>
<ng-template #loadingBlocks> <ng-template #loadingBlocks>
<div class="mempool-blocks-container" [class.time-ltr]="timeLtr"> <div class="mempool-blocks-container" [class.time-ltr]="timeLtr" [style.--block-size]="blockWidth+'px'">
<div class="flashing"> <div class="flashing">
<ng-template ngFor let-projectedBlock [ngForOf]="mempoolEmptyBlocks" let-i="index" [ngForTrackBy]="trackByFn"> <ng-template ngFor let-projectedBlock [ngForOf]="mempoolEmptyBlocks" let-i="index" [ngForTrackBy]="trackByFn">
<div class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="mempoolEmptyBlockStyles[i]"></div> <div class="bitcoin-block text-center mempool-block" [class.hide-block]="count && i >= count" id="mempool-block-{{ i }}" [ngStyle]="mempoolEmptyBlockStyles[i]"></div>
</ng-template> </ng-template>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
.bitcoin-block { .bitcoin-block {
width: 125px; width: var(--block-size);
height: 125px; height: var(--block-size);
transition: background 2s, right 2s, transform 1s, opacity 1s; transition: background 2s, right 2s, transform 1s, opacity 1s;
} }
@ -14,6 +14,7 @@
top: 0px; top: 0px;
right: 0px; right: 0px;
left: 0px; left: 0px;
--block-size: 125px;
} }
.flashing { .flashing {
@ -66,11 +67,11 @@
.bitcoin-block::after { .bitcoin-block::after {
content: ''; content: '';
width: 125px; width: var(--block-size);
height: 24px; height: calc(0.192 * var(--block-size));
position:absolute; position:absolute;
top: -24px; top: calc(-0.192 * var(--block-size));
left: -20px; left: calc(-0.16 * var(--block-size));
background-color: #232838; background-color: #232838;
transform:skew(40deg); transform:skew(40deg);
transform-origin:top; transform-origin:top;
@ -79,11 +80,11 @@
.bitcoin-block::before { .bitcoin-block::before {
content: ''; content: '';
width: 20px; width: calc(0.16 * var(--block-size));
height: 125px; height: var(--block-size);
position: absolute; position: absolute;
top: -12px; top: calc(-0.096 * var(--block-size));
left: -20px; left: calc(-0.16 * var(--block-size));
background-color: #191c27; background-color: #191c27;
z-index: -1; z-index: -1;
@ -100,7 +101,7 @@
background-color: #2d2825; background-color: #2d2825;
} }
.mempool-block.last-block { .mempool-block.hide-block {
opacity: 0; opacity: 0;
} }
@ -145,7 +146,7 @@
.bitcoin-block::before { .bitcoin-block::before {
transform: skewY(-50deg); transform: skewY(-50deg);
left: 125px; left: var(--block-size);
} }
.block-body { .block-body {
transform: scaleX(-1); transform: scaleX(-1);

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, Input } from '@angular/core'; import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Subscription, Observable, fromEvent, merge, of, combineLatest } from 'rxjs'; import { Subscription, Observable, fromEvent, merge, of, combineLatest } from 'rxjs';
import { MempoolBlock } from '../../interfaces/websocket.interface'; import { MempoolBlock } from '../../interfaces/websocket.interface';
import { StateService } from '../../services/state.service'; import { StateService } from '../../services/state.service';
@ -23,8 +23,9 @@ import { animate, style, transition, trigger } from '@angular/animations';
])], ])],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class MempoolBlocksComponent implements OnInit, OnDestroy { export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
@Input() tiny: boolean = false; @Input() minimal: boolean = false;
@Input() blockWidth: number = 125;
@Input() count: number = null; @Input() count: number = null;
specialBlocks = specialBlocks; specialBlocks = specialBlocks;
@ -51,8 +52,9 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
timeLtr: boolean; timeLtr: boolean;
animateEntry: boolean = false; animateEntry: boolean = false;
blockWidth = 125; blockOffset: number = 155;
blockPadding = 30; blockPadding: number = 30;
containerOffset: number = 40;
arrowVisible = false; arrowVisible = false;
tabHidden = false; tabHidden = false;
feeRounding = '1.0-0'; feeRounding = '1.0-0';
@ -221,6 +223,14 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
}); });
} }
ngOnChanges(changes: SimpleChanges): void {
if (changes.blockWidth && this.blockWidth) {
this.blockPadding = 0.24 * this.blockWidth;
this.containerOffset = 0.32 * this.blockWidth;
this.blockOffset = this.blockWidth + this.blockPadding;
}
}
ngOnDestroy() { ngOnDestroy() {
this.markBlocksSubscription.unsubscribe(); this.markBlocksSubscription.unsubscribe();
this.blockSubscription.unsubscribe(); this.blockSubscription.unsubscribe();
@ -243,12 +253,13 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2; const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2;
let blocksAmount; let blocksAmount;
if (this.count) { if (this.count) {
blocksAmount = this.count; blocksAmount = 8;
} else { } else {
blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding))); blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding)));
} }
while (blocks.length > blocksAmount) { while (blocks.length > blocksAmount) {
const block = blocks.pop(); const block = blocks.pop();
if (!this.count) {
const lastBlock = blocks[blocks.length - 1]; const lastBlock = blocks[blocks.length - 1];
lastBlock.blockSize += block.blockSize; lastBlock.blockSize += block.blockSize;
lastBlock.blockVSize += block.blockVSize; lastBlock.blockVSize += block.blockVSize;
@ -258,6 +269,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
lastBlock.medianFee = this.median(lastBlock.feeRange); lastBlock.medianFee = this.median(lastBlock.feeRange);
lastBlock.totalFees += block.totalFees; lastBlock.totalFees += block.totalFees;
} }
}
if (blocks.length) { if (blocks.length) {
blocks[blocks.length - 1].isStack = blocks[blocks.length - 1].blockVSize > this.stateService.blockVSize; blocks[blocks.length - 1].isStack = blocks[blocks.length - 1].blockVSize > this.stateService.blockVSize;
} }
@ -302,14 +314,14 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
}); });
return { return {
'right': 40 + index * 155 + 'px', 'right': this.containerOffset + index * this.blockOffset + 'px',
'background': backgroundGradients.join(',') + ')' 'background': backgroundGradients.join(',') + ')'
}; };
} }
getStyleForMempoolEmptyBlock(index: number) { getStyleForMempoolEmptyBlock(index: number) {
return { return {
'right': 40 + index * 155 + 'px', 'right': this.containerOffset + index * this.blockOffset + 'px',
'background': '#554b45', 'background': '#554b45',
}; };
} }

View File

@ -93,6 +93,7 @@ import { GlobalFooterComponent } from './components/global-footer/global-footer.
import { MempoolBlockOverviewComponent } from '../components/mempool-block-overview/mempool-block-overview.component'; import { MempoolBlockOverviewComponent } from '../components/mempool-block-overview/mempool-block-overview.component';
import { ClockchainComponent } from '../components/clockchain/clockchain.component'; import { ClockchainComponent } from '../components/clockchain/clockchain.component';
import { ClockFaceComponent } from '../components/clock-face/clock-face.component'; import { ClockFaceComponent } from '../components/clock-face/clock-face.component';
import { ClockComponent } from '../components/clock/clock.component';
import { ClockAComponent } from '../components/clock/clock-a.component'; import { ClockAComponent } from '../components/clock/clock-a.component';
import { ClockBComponent } from '../components/clock/clock-b.component'; import { ClockBComponent } from '../components/clock/clock-b.component';
@ -181,6 +182,7 @@ import { ClockBComponent } from '../components/clock/clock-b.component';
MempoolBlockOverviewComponent, MempoolBlockOverviewComponent,
ClockchainComponent, ClockchainComponent,
ClockComponent,
ClockAComponent, ClockAComponent,
ClockBComponent, ClockBComponent,
ClockFaceComponent, ClockFaceComponent,
@ -294,6 +296,7 @@ import { ClockBComponent } from '../components/clock/clock-b.component';
MempoolBlockOverviewComponent, MempoolBlockOverviewComponent,
ClockchainComponent, ClockchainComponent,
ClockComponent,
ClockAComponent, ClockAComponent,
ClockBComponent, ClockBComponent,
ClockFaceComponent, ClockFaceComponent,