diff --git a/frontend/cypress/integration/mainnet/mainnet.spec.ts b/frontend/cypress/integration/mainnet/mainnet.spec.ts index fc2a79b7b..614e9e9cd 100644 --- a/frontend/cypress/integration/mainnet/mainnet.spec.ts +++ b/frontend/cypress/integration/mainnet/mainnet.spec.ts @@ -1,4 +1,4 @@ -import { emitMempoolInfo, emitWithoutMempoolInfo } from "../../support/websocket"; +import { emitMempoolInfo, dropWebSocket } from "../../support/websocket"; describe('Mainnet', () => { beforeEach(() => { @@ -31,6 +31,23 @@ describe('Mainnet', () => { }); }); + it('loads dashboard, drop websocket and reconnect', () => { + cy.viewport('macbook-16'); + cy.mockMempoolSocket(); + cy.visit('/'); + cy.get('.badge').should('not.exist'); + dropWebSocket(); + cy.get('.badge').should('be.visible'); + cy.get('.badge', {timeout: 25000}).should('not.exist'); + emitMempoolInfo({ + 'params': { + loaded: true + } + }); + cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist'); + cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist'); + cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist'); + }); it('loads the dashboard', () => { cy.visit('/'); diff --git a/frontend/cypress/support/index.d.ts b/frontend/cypress/support/index.d.ts new file mode 100644 index 000000000..a6bef9f7c --- /dev/null +++ b/frontend/cypress/support/index.d.ts @@ -0,0 +1,10 @@ + +/// +declare namespace Cypress { + interface Chainable { + waitForSkeletonGone(): Chainable + waitForPageIdle(): Chainable + mockMempoolSocket(): Chainable + changeNetwork(network: "testnet"|"signet"|"liquid"|"bisq"|"mainnet"): Chainable + } +} \ No newline at end of file diff --git a/frontend/cypress/support/websocket.ts b/frontend/cypress/support/websocket.ts index ce971fae9..03f4a54f1 100644 --- a/frontend/cypress/support/websocket.ts +++ b/frontend/cypress/support/websocket.ts @@ -38,6 +38,7 @@ export const mockWebSocket = () => { win.mockServer = server; win.mockServer.on('connection', (socket) => { win.mockSocket = socket; + win.mockSocket.send('{"action":"init"}'); }); win.mockServer.on('message', (message) => { @@ -81,4 +82,11 @@ export const emitMempoolInfo = ({ }); cy.waitForSkeletonGone(); return cy.get('#mempool-block-0'); -}; \ No newline at end of file +}; + +export const dropWebSocket = (() => { + cy.window().then((win) => { + win.mockServer.simulate("error"); + }); + return cy.wait(500); +}); \ No newline at end of file diff --git a/frontend/cypress/tsconfig.json b/frontend/cypress/tsconfig.json index 79d78d7ec..8f044958a 100644 --- a/frontend/cypress/tsconfig.json +++ b/frontend/cypress/tsconfig.json @@ -2,7 +2,9 @@ "extends": "../tsconfig.json", "include": ["**/*.ts"], "compilerOptions": { - "sourceMap": false, - "types": ["cypress"] + "types": ["cypress"], + "lib": ["es2015", "dom"], + "allowJs": true, + "noEmit": true, } } diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html index ab2ffbe65..2d27a5dd2 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -1,4 +1,4 @@ -
+
  @@ -28,8 +28,8 @@
-
-
+
+
diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts index 9856cdfc1..751025fbb 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, Input } from '@angular/core'; -import { Subscription } from 'rxjs'; +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'; @@ -13,17 +13,18 @@ import { Router } from '@angular/router'; export class BlockchainBlocksComponent implements OnInit, OnDestroy { network = ''; - blocks: Block[] = this.mountEmptyBlocks(); + blocks: Block[] = []; + emptyBlocks: Block[] = this.mountEmptyBlocks(); markHeight: number; blocksSubscription: Subscription; networkSubscription: Subscription; tabHiddenSubscription: Subscription; markBlockSubscription: Subscription; - isLoadingWebsocketSubscription: Subscription; + loadingBlocks$: Observable; blockStyles = []; + emptyBlockStyles = []; interval: any; tabHidden = false; - loadingBlocks = false; arrowVisible = false; arrowLeftPx = 30; @@ -45,11 +46,10 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy { ) { } ngOnInit() { - this.blocks.forEach((b) => this.blockStyles.push(this.getStyleForBlock(b))); - this.isLoadingWebsocketSubscription = this.stateService.isLoadingWebSocket$.subscribe((loading) => this.loadingBlocks = loading); + this.emptyBlocks.forEach((b) => this.emptyBlockStyles.push(this.getStyleForEmptyBlock(b))); + this.loadingBlocks$ = this.stateService.isLoadingWebSocket$; this.networkSubscription = this.stateService.networkChanged$.subscribe((network) => this.network = network); this.tabHiddenSubscription = this.stateService.isTabHidden$.subscribe((tabHidden) => this.tabHidden = tabHidden); - this.blocksSubscription = this.stateService.blocks$ .subscribe(([block, txConfirmed]) => { if (this.blocks.some((b) => b.height === block.height)) { @@ -75,15 +75,13 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy { this.moveArrowToPosition(true, false); } - if (!this.loadingBlocks) { + this.blockStyles = []; + this.blocks.forEach((b) => this.blockStyles.push(this.getStyleForBlock(b))); + setTimeout(() => { this.blockStyles = []; this.blocks.forEach((b) => this.blockStyles.push(this.getStyleForBlock(b))); - setTimeout(() => { - this.blockStyles = []; - this.blocks.forEach((b) => this.blockStyles.push(this.getStyleForBlock(b))); - this.cd.markForCheck(); - }, 50); - } + this.cd.markForCheck(); + }, 50); if (this.blocks.length === this.stateService.env.KEEP_BLOCKS_AMOUNT) { this.blocksFilled = true; @@ -129,7 +127,6 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy { this.networkSubscription.unsubscribe(); this.tabHiddenSubscription.unsubscribe(); this.markBlockSubscription.unsubscribe(); - this.isLoadingWebsocketSubscription.unsubscribe(); clearInterval(this.interval); } @@ -186,9 +183,24 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy { )`, }; } + + getStyleForEmptyBlock(block: Block) { + let addLeft = 0; + + if (block.stage === 1) { + block.stage = 2; + addLeft = -205; + } + + return { + left: addLeft + 155 * this.emptyBlocks.indexOf(block) + 'px', + background: "#2d3348", + }; + } + mountEmptyBlocks() { const emptyBlocks = []; - for (let i = 0; i < 9; i++) { + for (let i = 0; i < 8; i++) { emptyBlocks.push({ id: '', height: 0, diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html index 9cbfe3ffa..4df2c9f2f 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html @@ -1,47 +1,51 @@ -
-
- -
-   -
-
- ~{{ projectedBlock.medianFee | number:'1.0-0' }} sat/vB -
-
- {{ projectedBlock.feeRange[0] | number:'1.0-0' }} - {{ projectedBlock.feeRange[projectedBlock.feeRange.length - 1] | number:'1.0-0' }} sat/vB -
-
-
- - {{ i }} transaction - {{ i }} transactions -
-
- - - - - - -
- -
- () - {{ i }} blocks + +
+
+ +
+   +
+
+ ~{{ projectedBlock.medianFee | number:'1.0-0' }} sat/vB
- +
+ {{ projectedBlock.feeRange[0] | number:'1.0-0' }} - {{ projectedBlock.feeRange[projectedBlock.feeRange.length - 1] | number:'1.0-0' }} sat/vB +
+
+
+ + {{ i }} transaction + {{ i }} transactions +
+
+ + + + + + +
+ +
+ () + {{ i }} blocks +
+
+
+
- -
- + +
+
-
-
+ -
-
- -
-
+ +
+
+ +
+
+
-
+ 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 34241205b..fabd9831d 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts @@ -3,7 +3,7 @@ import { Subscription, Observable, fromEvent, merge, of, combineLatest, timer } import { MempoolBlock } from 'src/app/interfaces/websocket.interface'; import { StateService } from 'src/app/services/state.service'; import { Router } from '@angular/router'; -import { take, map, switchMap } from 'rxjs/operators'; +import { take, map, switchMap, share } from 'rxjs/operators'; import { feeLevels, mempoolFeeColors } from 'src/app/app.constants'; @Component({ @@ -14,12 +14,15 @@ import { feeLevels, mempoolFeeColors } from 'src/app/app.constants'; }) export class MempoolBlocksComponent implements OnInit, OnDestroy { - mempoolBlocks: MempoolBlock[] = this.mountEmptyBlocks(); + mempoolBlocks: MempoolBlock[] = []; + mempoolEmptyBlocks: MempoolBlock[] = this.mountEmptyBlocks(); mempoolBlocks$: Observable; timeAvg$: Observable; + loadingBlocks$: Observable; - mempoolBlocksFull: MempoolBlock[] = this.mountEmptyBlocks(); + mempoolBlocksFull: MempoolBlock[] = []; mempoolBlockStyles = []; + mempoolEmptyBlockStyles = []; markBlocksSubscription: Subscription; isLoadingWebsocketSubscription: Subscription; blockSubscription: Subscription; @@ -31,7 +34,6 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { blockPadding = 30; arrowVisible = false; tabHidden = false; - loadingMempoolBlocks = true; rightPosition = 0; transition = '2s'; @@ -50,17 +52,18 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { ) { } ngOnInit() { + this.mempoolEmptyBlocks.forEach((b) => { + this.mempoolEmptyBlockStyles.push(this.getStyleForMempoolEmptyBlock(b.index)); + }); + this.reduceMempoolBlocksToFitScreen(this.mempoolEmptyBlocks); + this.mempoolBlocks.map(() => { this.updateMempoolBlockStyles(); this.calculateTransactionPosition(); }); this.reduceMempoolBlocksToFitScreen(this.mempoolBlocks); this.stateService.isTabHidden$.subscribe((tabHidden) => this.tabHidden = tabHidden); - - this.isLoadingWebsocketSubscription = this.stateService.isLoadingWebSocket$.subscribe((loading) => { - this.loadingMempoolBlocks = loading; - this.cd.markForCheck(); - }); + this.loadingBlocks$ = this.stateService.isLoadingWebSocket$; this.mempoolBlocks$ = merge( of(true), @@ -111,8 +114,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { } else { timeAvgMins += Math.abs(timeAvgDiff); } - - this.loadingMempoolBlocks = false; + return timeAvgMins * 60 * 1000; }) ); @@ -170,7 +172,6 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { this.markBlocksSubscription.unsubscribe(); this.blockSubscription.unsubscribe(); this.networkSubscription.unsubscribe(); - this.isLoadingWebsocketSubscription.unsubscribe(); clearTimeout(this.resetTransitionTimeout); } @@ -237,6 +238,13 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { }; } + getStyleForMempoolEmptyBlock(index: number) { + return { + 'right': 40 + index * 155 + 'px', + 'background': '#554b45', + }; + } + calculateTransactionPosition() { if ((!this.txFeePerVSize && (this.markIndex === undefined || this.markIndex === -1)) || !this.mempoolBlocks) { this.arrowVisible = false; @@ -289,7 +297,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { blockSize: 0, blockVSize: 0, feeRange: [], - index: 0, + index: i, medianFee: 0, nTx: 0, totalFees: 0