Add next and previous arrows to blocks.

This commit is contained in:
Miguel Medeiros 2021-08-12 19:49:39 -03:00
parent f3b470b63e
commit 6d070e75b0
8 changed files with 307 additions and 27 deletions

View File

@ -54,6 +54,126 @@ describe('Mainnet', () => {
cy.waitForSkeletonGone();
});
describe('blocks navigation', () => {
describe('keyboard events', () => {
it('loads first blockchain blocks visible and keypress arrow right', () => {
cy.viewport('macbook-16');
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('.blockchain-blocks-0 > a').click().then(() => {
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.waitForPageIdle();
cy.document().right();
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
});
});
it('loads first blockchain blocks visible and keypress arrow left', () => {
cy.viewport('macbook-16');
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('.blockchain-blocks-0 > a').click().then(() => {
cy.waitForPageIdle();
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.document().left();
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
});
});
it('loads last blockchain blocks and keypress arrow right', () => {
cy.viewport('macbook-16');
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('.blockchain-blocks-4 > a').click().then(() => {
cy.waitForPageIdle();
// block 6
cy.document().right();
cy.wait(5000);
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
// block 7
cy.document().right();
cy.wait(5000);
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
// block 8 - last visible block
cy.document().right();
cy.wait(5000);
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
// block 9 - not visible at the blochchain blocks visible block
cy.document().right();
cy.wait(5000);
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
});
});
it('loads genesis block and keypress arrow right', () => {
cy.viewport('macbook-16');
cy.visit('/block/0');
cy.waitForSkeletonGone();
cy.waitForPageIdle();
cy.document().right();
cy.wait(5000);
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
});
it('loads genesis block and keypress arrow left', () => {
cy.viewport('macbook-16');
cy.visit('/block/0');
cy.waitForSkeletonGone();
cy.waitForPageIdle();
cy.document().left();
cy.wait(5000);
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
});
});
describe('mouse events', () => {
it('loads first blockchain blocks visible and click on the arrow right', () => {
cy.viewport('macbook-16');
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('.blockchain-blocks-0 > a').click().then(() => {
cy.waitForPageIdle();
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').click().then(() => {
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
});
});
});
it('loads genesis block and click on the arrow left', () => {
cy.viewport('macbook-16');
cy.visit('/block/0');
cy.waitForSkeletonGone();
cy.waitForPageIdle();
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').click().then(() => {
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
});
});
});
});
it('loads skeleton when changes between networks', () => {
cy.visit('/');
cy.waitForSkeletonGone();

View File

@ -42,10 +42,20 @@
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
'use strict'
import 'cypress-wait-until';
import { PageIdleDetector } from './PageIdleDetector';
import { mockWebSocket } from './websocket';
/* global Cypress */
const codes = {
ArrowLeft: 37,
ArrowUp: 38,
ArrowRight: 39,
ArrowDown: 40
}
Cypress.Commands.add('waitForSkeletonGone', () => {
cy.waitUntil(() => {
return Cypress.$('.skeleton-loader').length === 0;
@ -75,3 +85,63 @@ Cypress.Commands.add('changeNetwork', (network: "testnet"|"signet"|"liquid"|"bis
});
});
});
// https://github.com/bahmutov/cypress-arrows/blob/8f0303842a343550fbeaf01528d01d1ff213b70c/src/index.js
function keydownCommand ($el, key) {
const message = `sending the "${key}" keydown event`
const log = Cypress.log({
name: `keydown: ${key}`,
message: message,
consoleProps: function () {
return {
Subject: $el
}
}
})
const e = $el.createEvent('KeyboardEvent')
Object.defineProperty(e, 'key', {
get: function () {
return key
}
})
Object.defineProperty(e, 'keyCode', {
get: function () {
return this.keyCodeVal
}
})
Object.defineProperty(e, 'which', {
get: function () {
return this.keyCodeVal
}
})
var metaKey = false
Object.defineProperty(e, 'metaKey', {
get: function () {
return metaKey
}
})
Object.defineProperty(e, 'shiftKey', {
get: function () {
return false
}
})
e.keyCodeVal = codes[key]
e.initKeyboardEvent('keydown', true, true,
$el.defaultView, false, false, false, false, e.keyCodeVal, e.keyCodeVal)
$el.dispatchEvent(e)
log.snapshot().end()
return $el
}
Cypress.Commands.add('keydown', { prevSubject: "dom" }, keydownCommand)
Cypress.Commands.add('left', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowLeft'))
Cypress.Commands.add('right', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowRight'))
Cypress.Commands.add('up', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowUp'))
Cypress.Commands.add('down', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowDown'))

View File

@ -44,7 +44,7 @@ import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
import { FeesBoxComponent } from './components/fees-box/fees-box.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { faAngleDown, faAngleUp, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle,
import { faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle,
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons';
import { ApiDocsComponent } from './components/api-docs/api-docs.component';
import { CodeTemplateComponent } from './components/api-docs/code-template.component';
@ -145,5 +145,7 @@ export class AppModule {
library.addIcons(faSortUp);
library.addIcons(faCaretUp);
library.addIcons(faCaretDown);
library.addIcons(faAngleRight);
library.addIcons(faAngleLeft);
}
}

View File

@ -1,7 +1,39 @@
<div class="container-xl" (window:resize)="onResize($event)">
<div class="title-block" id="block">
<h1><ng-template [ngIf]="blockHeight === 0" i18n="block.genesis">Genesis </ng-template><ng-template [ngIf]="blockHeight" i18n="block.block">Block <a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a></ng-template></h1>
<h1>
<ng-template [ngIf]="blockHeight === 0" i18n="block.genesis">Genesis
<div class="next-previous-blocks">
<a *ngIf="showNextBlocklink" [routerLink]="['/block/' | relativeUrl, nextBlockHeight]" i18n-ngbTooltip="Next Block" ngbTooltip="Next Block" placement="bottom">
<fa-icon [icon]="['fas', 'angle-left']" [fixedWidth]="true" i18n-title="dashboard.collapse" title="Collapse"></fa-icon>
</a>
<a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a>
<span placement="bottom" class="disable">
<fa-icon [icon]="['fas', 'angle-right']" [fixedWidth]="true" i18n-title="dashboard.collapse" title="Collapse"></fa-icon>
</span>
</div>
</ng-template>
<ng-template [ngIf]="blockHeight" i18n="block.block"> Block
<div class="next-previous-blocks">
<a *ngIf="showNextBlocklink" [routerLink]="['/block/' | relativeUrl, nextBlockHeight]" i18n-ngbTooltip="Next Block" ngbTooltip="Next Block" placement="bottom">
<fa-icon [icon]="['fas', 'angle-left']" [fixedWidth]="true" i18n-title="dashboard.collapse" title="Collapse"></fa-icon>
</a>
<span *ngIf="!showNextBlocklink" placement="bottom" class="disable">
<fa-icon [icon]="['fas', 'angle-left']" [fixedWidth]="true" i18n-title="dashboard.collapse" title="Collapse"></fa-icon>
</span>
<a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a>
<a *ngIf="showPreviousBlocklink" [routerLink]="['/block/' | relativeUrl, previousBlockHeight]" i18n-ngbTooltip="Previous Block" ngbTooltip="Previous Block" placement="bottom">
<fa-icon [icon]="['fas', 'angle-right']" [fixedWidth]="true" i18n-title="dashboard.collapse" title="Collapse"></fa-icon>
</a>
<span *ngIf="!showPreviousBlocklink" placement="bottom" class="disable">
<fa-icon [icon]="['fas', 'angle-right']" [fixedWidth]="true" i18n-title="dashboard.collapse" title="Collapse"></fa-icon>
</span>
</div>
</ng-template>
</h1>
<div class="grow"></div>
<button [routerLink]="['/' | relativeUrl]" class="btn btn-sm">&#10005;</button>
</div>

View File

@ -44,6 +44,11 @@ h1 {
float: left;
margin-right: 10px;
}
a {
&:hover, &:focus{
text-decoration: none;;
}
}
}
.address-link {
@ -110,4 +115,29 @@ h1 {
line-height: 1;
}
}
}
.grow {
flex-grow: 1;
}
.next-previous-blocks {
font-size: 36px;
display: inline-block;
vertical-align: bottom;
a {
color: #1ad8f4;
&:hover, &:focus {
color: #09a3ba;
display: inline-block;
// transform: scale(1.2);
// transition: 150ms all ease-in-out;
}
}
}
.disable {
font-size: 36px;
color: #393e5c73;
}

View File

@ -18,6 +18,8 @@ export class BlockComponent implements OnInit, OnDestroy {
network = '';
block: Block;
blockHeight: number;
previousBlockHeight: number;
nextBlockHeight: number;
blockHash: string;
isLoadingBlock = true;
latestBlock: Block;
@ -33,6 +35,8 @@ export class BlockComponent implements OnInit, OnDestroy {
itemsPerPage: number;
txsLoadingStatus$: Observable<number>;
showDetails = false;
showPreviousBlocklink = true;
showNextBlocklink = true;
constructor(
private route: ActivatedRoute,
@ -69,6 +73,9 @@ export class BlockComponent implements OnInit, OnDestroy {
if (history.state.data && history.state.data.blockHeight) {
this.blockHeight = history.state.data.blockHeight;
this.previousBlockHeight = history.state.data.blockHeight - 1;
this.nextBlockHeight = history.state.data.blockHeight + 1;
this.setNextAndPreviousBlockLink();
}
let isBlockHeight = false;
@ -81,6 +88,9 @@ export class BlockComponent implements OnInit, OnDestroy {
if (history.state.data && history.state.data.block) {
this.blockHeight = history.state.data.block.height;
this.previousBlockHeight = history.state.data.block.height - 1;
this.nextBlockHeight = history.state.data.block.height + 1;
this.setNextAndPreviousBlockLink();
return of(history.state.data.block);
} else {
this.isLoadingBlock = true;
@ -103,6 +113,10 @@ export class BlockComponent implements OnInit, OnDestroy {
tap((block: Block) => {
this.block = block;
this.blockHeight = block.height;
this.previousBlockHeight = block.height - 1;
this.nextBlockHeight = block.height + 1;
this.setNextAndPreviousBlockLink();
this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`);
this.isLoadingBlock = false;
if (block.coinbaseTx) {
@ -141,7 +155,10 @@ export class BlockComponent implements OnInit, OnDestroy {
});
this.stateService.blocks$
.subscribe(([block]) => this.latestBlock = block);
.subscribe(([block]) => {
this.latestBlock = block;
this.setNextAndPreviousBlockLink();
});
this.stateService.networkChanged$
.subscribe((network) => this.network = network);
@ -153,6 +170,23 @@ export class BlockComponent implements OnInit, OnDestroy {
this.showDetails = false;
}
});
this.stateService.keyNavigation$.subscribe((event) => {
if (this.showPreviousBlocklink) {
if (event.key === 'ArrowRight') {
if (this.previousBlockHeight >= 0) {
this.router.navigate([(this.network ? '/' + this.network : '') + '/block/', this.previousBlockHeight]);
this.blockHeight = this.previousBlockHeight;
}
}
}
if (this.showNextBlocklink) {
if (event.key === 'ArrowLeft') {
this.router.navigate([(this.network ? '/' + this.network : '') + '/block/', this.nextBlockHeight]);
this.blockHeight = this.nextBlockHeight;
}
}
});
}
ngOnDestroy() {
@ -222,4 +256,18 @@ export class BlockComponent implements OnInit, OnDestroy {
onResize(event: any) {
this.paginationMaxSize = event.target.innerWidth < 670 ? 3 : 5;
}
setNextAndPreviousBlockLink(){
if (this.latestBlock && this.blockHeight){
if (this.blockHeight === 0){
this.showPreviousBlocklink = false;
} else {
this.showPreviousBlocklink = true;
}
if (this.latestBlock.height && this.latestBlock.height === this.blockHeight) {
this.showNextBlocklink = false;
}else{
this.showNextBlocklink = true;
}
}
}
}

View File

@ -1,6 +1,6 @@
<div class="blocks-container" *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
<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" 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]">
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" class="blockLink">&nbsp;</a>
<div class="block-height">
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>

View File

@ -98,28 +98,6 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
this.moveArrowToPosition(false);
this.cd.markForCheck();
});
this.stateService.keyNavigation$.subscribe((event) => {
if (!this.markHeight) {
return;
}
if (event.key === 'ArrowRight') {
const blockindex = this.blocks.findIndex((b) => b.height === this.markHeight);
if (this.blocks[blockindex + 1]) {
this.router.navigate([(this.network ? '/' + this.network : '') + '/block/',
this.blocks[blockindex + 1].id], { state: { data: { block: this.blocks[blockindex + 1] } } });
}
} else if (event.key === 'ArrowLeft') {
const blockindex = this.blocks.findIndex((b) => b.height === this.markHeight);
if (blockindex === 0) {
this.router.navigate([(this.network ? '/' + this.network : '') + '/mempool-block/', '0']);
} else {
this.router.navigate([(this.network ? '/' + this.network : '') + '/block/',
this.blocks[blockindex - 1].id], { state: { data: { block: this.blocks[blockindex - 1] }}});
}
}
});
}
ngOnDestroy() {