From 6d070e75b027f3c602eabad4f1ecf7729643cd7a Mon Sep 17 00:00:00 2001 From: Miguel Medeiros Date: Thu, 12 Aug 2021 19:49:39 -0300 Subject: [PATCH] Add next and previous arrows to blocks. --- .../integration/mainnet/mainnet.spec.ts | 120 ++++++++++++++++++ frontend/cypress/support/commands.ts | 70 ++++++++++ frontend/src/app/app.module.ts | 4 +- .../app/components/block/block.component.html | 34 ++++- .../app/components/block/block.component.scss | 30 +++++ .../app/components/block/block.component.ts | 50 +++++++- .../blockchain-blocks.component.html | 4 +- .../blockchain-blocks.component.ts | 22 ---- 8 files changed, 307 insertions(+), 27 deletions(-) diff --git a/frontend/cypress/integration/mainnet/mainnet.spec.ts b/frontend/cypress/integration/mainnet/mainnet.spec.ts index 614e9e9cd..3bd3b1369 100644 --- a/frontend/cypress/integration/mainnet/mainnet.spec.ts +++ b/frontend/cypress/integration/mainnet/mainnet.spec.ts @@ -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(); diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts index b240a7bc4..e0d07d517 100644 --- a/frontend/cypress/support/commands.ts +++ b/frontend/cypress/support/commands.ts @@ -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')) \ No newline at end of file diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index c9d6145b6..ecfdcb11e 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -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); } } diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index 34cc270c0..f12383deb 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -1,7 +1,39 @@
-

Genesis Block {{ blockHeight }}

+

+ Genesis + + + Block +
+ + + + + + + {{ blockHeight }} + + + + + + +
+
+

+ +
+
diff --git a/frontend/src/app/components/block/block.component.scss b/frontend/src/app/components/block/block.component.scss index 9813b0293..46ade1ab4 100644 --- a/frontend/src/app/components/block/block.component.scss +++ b/frontend/src/app/components/block/block.component.scss @@ -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; } \ No newline at end of file diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index 38ebc43a1..55f1d3d35 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -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; 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; + } + } + } } 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 2d27a5dd2..b2acffcf0 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -1,6 +1,6 @@ -
+
-
+
 
{{ block.height }} 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 751025fbb..70d5d0d5d 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts @@ -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() {