-
0">
+
0" [allBlocks]="scrollableMempool" (widthChange)="onMempoolWidthChange($event)">
0">
diff --git a/frontend/src/app/components/blockchain/blockchain.component.scss b/frontend/src/app/components/blockchain/blockchain.component.scss
index 63ca22626..135a8b842 100644
--- a/frontend/src/app/components/blockchain/blockchain.component.scss
+++ b/frontend/src/app/components/blockchain/blockchain.component.scss
@@ -26,43 +26,14 @@
position: absolute;
left: 0;
top: 75px;
- transform: translateX(50vw);
+ --divider-offset: 50vw;
+ --mempool-offset: 0px;
+ transform: translateX(calc(var(--divider-offset) + var(--mempool-offset)));
}
-.position-container.liquid, .position-container.liquidtestnet {
- transform: translateX(420px);
-}
-
-@media (min-width: 768px) {
- .blockchain-wrapper.time-ltr {
- .position-container.liquid, .position-container.liquidtestnet {
- transform: translateX(calc(100vw - 420px));
- }
- }
-}
-
-@media (max-width: 767.98px) {
- .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);
- }
+.blockchain-wrapper.time-ltr {
+ .position-container {
+ transform: translateX(calc(100vw - var(--divider-offset) - var(--mempool-offset)));
}
}
diff --git a/frontend/src/app/components/blockchain/blockchain.component.ts b/frontend/src/app/components/blockchain/blockchain.component.ts
index ab9875a4c..5eb2ed481 100644
--- a/frontend/src/app/components/blockchain/blockchain.component.ts
+++ b/frontend/src/app/components/blockchain/blockchain.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, Output, EventEmitter, HostListener, ChangeDetectorRef } from '@angular/core';
import { firstValueFrom, Subscription } from 'rxjs';
import { StateService } from '../../services/state.service';
@@ -13,43 +13,95 @@ export class BlockchainComponent implements OnInit, OnDestroy {
@Input() pageIndex: number;
@Input() blocksPerPage: number = 8;
@Input() minScrollWidth: number = 0;
+ @Input() scrollableMempool: boolean = false;
+
+ @Output() mempoolOffsetChange: EventEmitter = new EventEmitter();
network: string;
timeLtrSubscription: Subscription;
timeLtr: boolean = this.stateService.timeLtr.value;
ltrTransitionEnabled = false;
+ flipping = false;
connectionStateSubscription: Subscription;
loadingTip: boolean = true;
connected: boolean = true;
+ dividerOffset: number = 0;
+ mempoolOffset: number = 0;
+
constructor(
public stateService: StateService,
+ private cd: ChangeDetectorRef,
) {}
- ngOnInit() {
+ ngOnInit(): void {
+ this.onResize();
this.network = this.stateService.network;
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
this.timeLtr = !!ltr;
});
this.connectionStateSubscription = this.stateService.connectionState$.subscribe(state => {
this.connected = (state === 2);
- })
- firstValueFrom(this.stateService.chainTip$).then(tip => {
+ });
+ firstValueFrom(this.stateService.chainTip$).then(() => {
this.loadingTip = false;
});
}
- ngOnDestroy() {
+ ngOnDestroy(): void {
this.timeLtrSubscription.unsubscribe();
this.connectionStateSubscription.unsubscribe();
}
- trackByPageFn(index: number, item: { index: number }) {
+ trackByPageFn(index: number, item: { index: number }): number {
return item.index;
}
- toggleTimeDirection() {
- this.ltrTransitionEnabled = true;
- this.stateService.timeLtr.next(!this.timeLtr);
+ toggleTimeDirection(): void {
+ this.ltrTransitionEnabled = false;
+ const prevOffset = this.mempoolOffset;
+ this.mempoolOffset = 0;
+ this.mempoolOffsetChange.emit(0);
+ setTimeout(() => {
+ this.ltrTransitionEnabled = true;
+ this.flipping = true;
+ this.stateService.timeLtr.next(!this.timeLtr);
+ setTimeout(() => {
+ this.ltrTransitionEnabled = false;
+ this.flipping = false;
+ this.mempoolOffset = prevOffset;
+ this.mempoolOffsetChange.emit(this.mempoolOffset);
+ }, 1000);
+ }, 0);
+ this.cd.markForCheck();
+ }
+
+ onMempoolWidthChange(width): void {
+ if (this.flipping) {
+ return;
+ }
+ this.mempoolOffset = Math.max(0, width - this.dividerOffset);
+ this.cd.markForCheck();
+ setTimeout(() => {
+ this.mempoolOffsetChange.emit(this.mempoolOffset);
+ }, 0);
+ }
+
+ @HostListener('window:resize', ['$event'])
+ onResize(): void {
+ if (window.innerWidth >= 768) {
+ if (this.stateService.isLiquid()) {
+ this.dividerOffset = 420;
+ } else {
+ this.dividerOffset = window.innerWidth * 0.5;
+ }
+ } else {
+ if (this.stateService.isLiquid()) {
+ this.dividerOffset = window.innerWidth * 0.5;
+ } else {
+ this.dividerOffset = window.innerWidth * 0.95;
+ }
+ }
+ this.cd.markForCheck();
}
}
diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts
index a72d24899..bc3633be0 100644
--- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts
+++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts
@@ -1,9 +1,9 @@
-import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, Input, OnChanges, SimpleChanges, Output, EventEmitter } from '@angular/core';
import { Subscription, Observable, fromEvent, merge, of, combineLatest } from 'rxjs';
import { MempoolBlock } from '../../interfaces/websocket.interface';
import { StateService } from '../../services/state.service';
import { Router } from '@angular/router';
-import { take, map, switchMap } from 'rxjs/operators';
+import { take, map, switchMap, tap } from 'rxjs/operators';
import { feeLevels, mempoolFeeColors } from '../../app.constants';
import { specialBlocks } from '../../app.constants';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
@@ -29,6 +29,9 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
@Input() count: number = null;
@Input() spotlight: number = 0;
@Input() getHref?: (index) => string = (index) => `/mempool-block/${index}`;
+ @Input() allBlocks: boolean = false;
+
+ @Output() widthChange: EventEmitter = new EventEmitter();
specialBlocks = specialBlocks;
mempoolBlocks: MempoolBlock[] = [];
@@ -147,7 +150,12 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
this.updateMempoolBlockStyles();
this.calculateTransactionPosition();
+
return this.mempoolBlocks;
+ }),
+ tap(() => {
+ this.cd.markForCheck();
+ this.widthChange.emit(this.containerOffset + this.mempoolBlocks.length * this.blockOffset);
})
);
@@ -257,7 +265,10 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
reduceEmptyBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2;
- const blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding)));
+ let blocksAmount = this.stateService.env.MEMPOOL_BLOCKS_AMOUNT;
+ if (!this.allBlocks) {
+ blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding)));
+ }
while (blocks.length < blocksAmount) {
blocks.push({
blockSize: 0,
@@ -277,10 +288,10 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2;
- let blocksAmount;
+ let blocksAmount = this.stateService.env.MEMPOOL_BLOCKS_AMOUNT;
if (this.count) {
blocksAmount = 8;
- } else {
+ } else if (!this.allBlocks) {
blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding)));
}
while (blocks.length > blocksAmount) {
diff --git a/frontend/src/app/components/start/start.component.html b/frontend/src/app/components/start/start.component.html
index ec4bf4805..5cf7b4fd9 100644
--- a/frontend/src/app/components/start/start.component.html
+++ b/frontend/src/app/components/start/start.component.html
@@ -18,7 +18,7 @@
(dragstart)="onDragStart($event)"
(scroll)="onScroll($event)"
>
-
+
diff --git a/frontend/src/app/components/start/start.component.ts b/frontend/src/app/components/start/start.component.ts
index 3bd0c086e..22d3d6350 100644
--- a/frontend/src/app/components/start/start.component.ts
+++ b/frontend/src/app/components/start/start.component.ts
@@ -1,6 +1,6 @@
import { Component, ElementRef, HostListener, OnInit, OnDestroy, ViewChild, Input } from '@angular/core';
import { Subscription } from 'rxjs';
-import { StateService } from '../../services/state.service';
+import { MarkBlockState, StateService } from '../../services/state.service';
import { specialBlocks } from '../../app.constants';
@Component({
@@ -24,6 +24,7 @@ export class StartComponent implements OnInit, OnDestroy {
chainTipSubscription: Subscription;
chainTip: number = -1;
tipIsSet: boolean = false;
+ lastMark: MarkBlockState;
markBlockSubscription: Subscription;
blockCounterSubscription: Subscription;
@ViewChild('blockchainContainer') blockchainContainer: ElementRef;
@@ -40,10 +41,11 @@ export class StartComponent implements OnInit, OnDestroy {
minScrollWidth: number;
pageIndex: number = 0;
pages: any[] = [];
- pendingMark: number | void = null;
+ pendingMark: number | null = null;
lastUpdate: number = 0;
lastMouseX: number;
velocity: number = 0;
+ mempoolOffset: number = 0;
constructor(
private stateService: StateService,
@@ -70,19 +72,40 @@ export class StartComponent implements OnInit, OnDestroy {
this.chainTip = height;
this.tipIsSet = true;
this.updatePages();
- if (this.pendingMark != null) {
- this.scrollToBlock(this.pendingMark);
- this.pendingMark = null;
- }
+ this.applyPendingMarkArrow();
});
this.markBlockSubscription = this.stateService.markBlock$.subscribe((mark) => {
+ let blockHeight;
+ let newMark = true;
if (mark?.blockHeight != null) {
+ if (this.lastMark?.blockHeight === mark.blockHeight) {
+ newMark = false;
+ }
+ blockHeight = mark.blockHeight;
+ } else if (mark?.mempoolBlockIndex != null) {
+ if (this.lastMark?.mempoolBlockIndex === mark.mempoolBlockIndex || (mark.txid && this.lastMark?.txid === mark.txid)) {
+ newMark = false;
+ }
+ blockHeight = -1 - mark.mempoolBlockIndex;
+ } else if (mark?.mempoolPosition?.block != null) {
+ if (this.lastMark?.txid === mark.txid) {
+ newMark = false;
+ }
+ blockHeight = -1 - mark.mempoolPosition.block;
+ }
+ this.lastMark = mark;
+ if (blockHeight != null) {
if (this.tipIsSet) {
- if (!this.blockInViewport(mark.blockHeight)) {
- this.scrollToBlock(mark.blockHeight);
+ let scrollToHeight = blockHeight;
+ if (blockHeight < 0) {
+ scrollToHeight = this.chainTip - blockHeight;
}
- } else {
- this.pendingMark = mark.blockHeight;
+ if (newMark && !this.blockInViewport(scrollToHeight)) {
+ this.scrollToBlock(scrollToHeight);
+ }
+ }
+ if (!this.tipIsSet || (blockHeight < 0 && !this.mempoolOffset)) {
+ this.pendingMark = blockHeight;
}
}
});
@@ -117,6 +140,24 @@ export class StartComponent implements OnInit, OnDestroy {
});
}
+ onMempoolOffsetChange(offset): void {
+ const delta = offset - this.mempoolOffset;
+ this.addConvertedScrollOffset(delta);
+ this.mempoolOffset = offset;
+ this.applyPendingMarkArrow();
+ }
+
+ applyPendingMarkArrow(): void {
+ if (this.pendingMark != null) {
+ if (this.pendingMark < 0) {
+ this.scrollToBlock(this.chainTip - this.pendingMark);
+ } else {
+ this.scrollToBlock(this.pendingMark);
+ }
+ this.pendingMark = null;
+ }
+ }
+
@HostListener('window:resize', ['$event'])
onResize(): void {
this.isMobile = window.innerWidth <= 767.98;
@@ -350,7 +391,7 @@ export class StartComponent implements OnInit, OnDestroy {
resetScroll(): void {
this.scrollToBlock(this.chainTip);
- this.blockchainContainer.nativeElement.scrollLeft = 0;
+ this.setScrollLeft(0);
}
getPageIndexOf(height: number): number {
@@ -368,9 +409,17 @@ export class StartComponent implements OnInit, OnDestroy {
getConvertedScrollOffset(): number {
if (this.timeLtr) {
- return -this.blockchainContainer?.nativeElement?.scrollLeft || 0;
+ return -(this.blockchainContainer?.nativeElement?.scrollLeft || 0) - this.mempoolOffset;
} else {
- return this.blockchainContainer?.nativeElement?.scrollLeft || 0;
+ return (this.blockchainContainer?.nativeElement?.scrollLeft || 0) - this.mempoolOffset;
+ }
+ }
+
+ setScrollLeft(offset: number): void {
+ if (this.timeLtr) {
+ this.blockchainContainer.nativeElement.scrollLeft = offset - this.mempoolOffset;
+ } else {
+ this.blockchainContainer.nativeElement.scrollLeft = offset + this.mempoolOffset;
}
}
@@ -388,7 +437,7 @@ export class StartComponent implements OnInit, OnDestroy {
ngOnDestroy() {
if (this.blockchainContainer?.nativeElement) {
// clean up scroll position to prevent caching wrong scroll in Firefox
- this.blockchainContainer.nativeElement.scrollLeft = 0;
+ this.setScrollLeft(0);
}
this.timeLtrSubscription.unsubscribe();
this.chainTipSubscription.unsubscribe();
diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts
index e7fbaa913..bbf679dcf 100644
--- a/frontend/src/app/components/transaction/transaction.component.ts
+++ b/frontend/src/app/components/transaction/transaction.component.ts
@@ -241,6 +241,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.mempoolPosition = txPosition.position;
if (this.tx && !this.tx.status.confirmed) {
this.stateService.markBlock$.next({
+ txid: txPosition.txid,
mempoolPosition: this.mempoolPosition
});
this.txInBlockIndex = this.mempoolPosition.block;
@@ -360,6 +361,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
} else {
if (tx.cpfpChecked) {
this.stateService.markBlock$.next({
+ txid: tx.txid,
txFeePerVSize: tx.effectiveFeePerVsize,
mempoolPosition: this.mempoolPosition,
});
diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts
index fb3b37e05..738893bbc 100644
--- a/frontend/src/app/services/state.service.ts
+++ b/frontend/src/app/services/state.service.ts
@@ -8,8 +8,9 @@ import { isPlatformBrowser } from '@angular/common';
import { map, scan, shareReplay, tap } from 'rxjs/operators';
import { StorageService } from './storage.service';
-interface MarkBlockState {
+export interface MarkBlockState {
blockHeight?: number;
+ txid?: string;
mempoolBlockIndex?: number;
txFeePerVSize?: number;
mempoolPosition?: MempoolPosition;