UI/UX: Fix blockchain-blocks skeleton. (#697)
* Fix blockchain-blocks skeleton. * Fix blockchain skeleton background. * Fix mempool blockchain skeleton. * Add e2e testing. Add tsconfig. Fix mempool fit to screen. * Fix wrong return. Fix e2e testing. * Fix blockchainblocks connectionstate. Add init action to websocket mock. Add e2e testing for droping websock connection. * Ref e2e code for websocket connection. Fix blockchain blocks skeleton. * Fix state connections. Remove .only e2e tests. * Fix mempool blocks skeleton. * Add fit screen to empty blocks.
This commit is contained in:
		
							parent
							
								
									35d4abcc25
								
							
						
					
					
						commit
						6dffbd40f3
					
				@ -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('/');
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										10
									
								
								frontend/cypress/support/index.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								frontend/cypress/support/index.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
 | 
			
		||||
/// <reference types="cypress" />
 | 
			
		||||
declare namespace Cypress {
 | 
			
		||||
    interface Chainable<Subject> {
 | 
			
		||||
        waitForSkeletonGone(): Chainable<any>
 | 
			
		||||
        waitForPageIdle(): Chainable<any>
 | 
			
		||||
        mockMempoolSocket(): Chainable<any>
 | 
			
		||||
        changeNetwork(network: "testnet"|"signet"|"liquid"|"bisq"|"mainnet"): Chainable<any>
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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) => {
 | 
			
		||||
@ -82,3 +83,10 @@ export const emitMempoolInfo = ({
 | 
			
		||||
    cy.waitForSkeletonGone();
 | 
			
		||||
    return cy.get('#mempool-block-0');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const dropWebSocket = (() => {
 | 
			
		||||
    cy.window().then((win) => {
 | 
			
		||||
        win.mockServer.simulate("error");
 | 
			
		||||
    });
 | 
			
		||||
    return cy.wait(500);
 | 
			
		||||
});
 | 
			
		||||
@ -2,7 +2,9 @@
 | 
			
		||||
  "extends": "../tsconfig.json",
 | 
			
		||||
  "include": ["**/*.ts"],
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "sourceMap": false,
 | 
			
		||||
    "types": ["cypress"]
 | 
			
		||||
    "types": ["cypress"],
 | 
			
		||||
    "lib": ["es2015", "dom"],
 | 
			
		||||
    "allowJs": true,
 | 
			
		||||
    "noEmit": true,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
<div class="blocks-container" *ngIf="!loadingBlocks; else loadingBlocksTemplate">
 | 
			
		||||
<div class="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" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]">
 | 
			
		||||
      <a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" class="blockLink"> </a>
 | 
			
		||||
@ -28,8 +28,8 @@
 | 
			
		||||
<ng-template #loadingBlocksTemplate >
 | 
			
		||||
  <div class="blocks-container">
 | 
			
		||||
    <div class="flashing">
 | 
			
		||||
      <div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" >
 | 
			
		||||
        <div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]"></div>
 | 
			
		||||
      <div *ngFor="let block of emptyBlocks; let i = index; trackBy: trackByBlocksFn" >
 | 
			
		||||
        <div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" [ngStyle]="emptyBlockStyles[i]"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
@ -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<boolean>;
 | 
			
		||||
  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,7 +75,6 @@ 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(() => {
 | 
			
		||||
@ -83,7 +82,6 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
 | 
			
		||||
          this.blocks.forEach((b) => this.blockStyles.push(this.getStyleForBlock(b)));
 | 
			
		||||
          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,
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
<div class="mempool-blocks-container">
 | 
			
		||||
  <div class="flashing" *ngIf="!loadingMempoolBlocks && (timeAvg$ | async) as timeAvg;">      
 | 
			
		||||
<ng-container *ngIf="(loadingBlocks$ | async) === false; else loadingBlocks">
 | 
			
		||||
  <div class="mempool-blocks-container" *ngIf="(timeAvg$ | async) as timeAvg;">
 | 
			
		||||
    <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]">
 | 
			
		||||
          <a [routerLink]="['/mempool-block/' | relativeUrl, i]" class="blockLink"> </a>
 | 
			
		||||
@ -37,11 +38,14 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div *ngIf="arrowVisible" id="arrow-up" [ngStyle]="{'right': rightPosition + 75 + 'px', transition: transition }"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</ng-container>
 | 
			
		||||
 | 
			
		||||
<div class="mempool-blocks-container" *ngIf="loadingMempoolBlocks === true">
 | 
			
		||||
<ng-template #loadingBlocks>
 | 
			
		||||
  <div class="mempool-blocks-container">
 | 
			
		||||
    <div class="flashing">
 | 
			
		||||
    <ng-template ngFor let-projectedBlock [ngForOf]="mempoolBlocks" let-i="index" [ngForTrackBy]="trackByFn">
 | 
			
		||||
      <div class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]"></div>
 | 
			
		||||
      <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>
 | 
			
		||||
      </ng-template>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
@ -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<MempoolBlock[]>;
 | 
			
		||||
  timeAvg$: Observable<number>;
 | 
			
		||||
  loadingBlocks$: Observable<boolean>;
 | 
			
		||||
 | 
			
		||||
  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),
 | 
			
		||||
@ -112,7 +115,6 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user