+
diff --git a/frontend/src/app/components/blockchain/blockchain.component.scss b/frontend/src/app/components/blockchain/blockchain.component.scss
index 990cf9535..2f13374fe 100644
--- a/frontend/src/app/components/blockchain/blockchain.component.scss
+++ b/frontend/src/app/components/blockchain/blockchain.component.scss
@@ -16,7 +16,7 @@
.blockchain-wrapper {
height: 250px;
- -webkit-user-select: none; /* Safari */
+ -webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10+/Edge */
user-select: none; /* Standard */
@@ -24,23 +24,46 @@
.position-container {
position: absolute;
- left: 50%;
+ left: 0;
top: 75px;
+ transform: translateX(50vw);
+ transition: transform 1s;
}
.position-container.liquid, .position-container.liquidtestnet {
- left: 420px;
+ 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) {
- .position-container {
- left: 95%;
+ .blockchain-wrapper {
+ .position-container {
+ transform: translateX(95vw);
+ }
+ .position-container.liquid, .position-container.liquidtestnet {
+ transform: translateX(50vw);
+ }
+ .position-container.loading {
+ transform: translateX(50vw);
+ }
}
- .position-container.liquid, .position-container.liquidtestnet {
- left: 50%;
- }
- .position-container.loading {
- left: 50%;
+ .blockchain-wrapper.time-ltr {
+ .position-container {
+ transform: translateX(5vw);
+ }
+ .position-container.liquid, .position-container.liquidtestnet {
+ transform: translateX(50vw);
+ }
+ .position-container.loading {
+ transform: translateX(50vw);
+ }
}
}
@@ -57,4 +80,31 @@
width: 300px;
left: -150px;
top: 0px;
+}
+
+.time-toggle {
+ color: white;
+ font-size: 1rem;
+ position: absolute;
+ bottom: -1.5em;
+ left: 1px;
+ transform: translateX(-50%);
+ background: none;
+ border: none;
+ outline: none;
+ margin: 0;
+ padding: 0;
+}
+
+.blockchain-wrapper.ltr-transition .blocks-wrapper,
+.blockchain-wrapper.ltr-transition .time-toggle {
+ transition: transform 1s;
+}
+
+.blockchain-wrapper.time-ltr .blocks-wrapper {
+ transform: scaleX(-1);
+}
+
+.blockchain-wrapper.time-ltr .time-toggle {
+ transform: translateX(-50%) scaleX(-1);
}
\ No newline at end of file
diff --git a/frontend/src/app/components/blockchain/blockchain.component.ts b/frontend/src/app/components/blockchain/blockchain.component.ts
index b8ae8d1d2..e99b3532d 100644
--- a/frontend/src/app/components/blockchain/blockchain.component.ts
+++ b/frontend/src/app/components/blockchain/blockchain.component.ts
@@ -1,4 +1,5 @@
-import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
+import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
+import { Subscription } from 'rxjs';
import { StateService } from '../../services/state.service';
@Component({
@@ -7,8 +8,11 @@ import { StateService } from '../../services/state.service';
styleUrls: ['./blockchain.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class BlockchainComponent implements OnInit {
+export class BlockchainComponent implements OnInit, OnDestroy {
network: string;
+ timeLtrSubscription: Subscription;
+ timeLtr: boolean = this.stateService.timeLtr.value;
+ ltrTransitionEnabled = false;
constructor(
public stateService: StateService,
@@ -16,5 +20,17 @@ export class BlockchainComponent implements OnInit {
ngOnInit() {
this.network = this.stateService.network;
+ this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
+ this.timeLtr = !!ltr;
+ });
+ }
+
+ ngOnDestroy() {
+ this.timeLtrSubscription.unsubscribe();
+ }
+
+ toggleTimeDirection() {
+ this.ltrTransitionEnabled = true;
+ this.stateService.timeLtr.next(!this.timeLtr);
}
}
diff --git a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.html b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.html
index 304b2a7f9..3cb4ff3e8 100644
--- a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.html
+++ b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.html
@@ -3,7 +3,7 @@
[isLoading]="isLoading$ | async"
[resolution]="75"
[blockLimit]="stateService.blockVSize"
- [orientation]="'left'"
+ [orientation]="timeLtr ? 'right' : 'left'"
[flip]="true"
(txClickEvent)="onTxClick($event)"
>
diff --git a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts
index b7fb65753..7a39e3536 100644
--- a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts
+++ b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts
@@ -1,5 +1,5 @@
import { Component, ComponentRef, ViewChild, HostListener, Input, Output, EventEmitter,
- OnDestroy, OnChanges, ChangeDetectionStrategy, AfterViewInit } from '@angular/core';
+ OnInit, OnDestroy, OnChanges, ChangeDetectionStrategy, ChangeDetectorRef, AfterViewInit } from '@angular/core';
import { StateService } from '../../services/state.service';
import { MempoolBlockDelta, TransactionStripped } from '../../interfaces/websocket.interface';
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
@@ -14,7 +14,7 @@ import { Router } from '@angular/router';
templateUrl: './mempool-block-overview.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class MempoolBlockOverviewComponent implements OnDestroy, OnChanges, AfterViewInit {
+export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit {
@Input() index: number;
@Output() txPreviewEvent = new EventEmitter
();
@@ -23,6 +23,10 @@ export class MempoolBlockOverviewComponent implements OnDestroy, OnChanges, Afte
lastBlockHeight: number;
blockIndex: number;
isLoading$ = new BehaviorSubject(true);
+ timeLtrSubscription: Subscription;
+ timeLtr: boolean;
+ chainDirection: string = 'right';
+ poolDirection: string = 'left';
blockSub: Subscription;
deltaSub: Subscription;
@@ -31,8 +35,18 @@ export class MempoolBlockOverviewComponent implements OnDestroy, OnChanges, Afte
public stateService: StateService,
private websocketService: WebsocketService,
private router: Router,
+ private cd: ChangeDetectorRef,
) { }
+ ngOnInit(): void {
+ this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
+ this.timeLtr = !!ltr;
+ this.chainDirection = ltr ? 'left' : 'right';
+ this.poolDirection = ltr ? 'right' : 'left';
+ this.cd.markForCheck();
+ });
+ }
+
ngAfterViewInit(): void {
this.blockSub = merge(
of(true),
@@ -50,7 +64,7 @@ export class MempoolBlockOverviewComponent implements OnDestroy, OnChanges, Afte
ngOnChanges(changes): void {
if (changes.index) {
if (this.blockGraph) {
- this.blockGraph.clear(changes.index.currentValue > changes.index.previousValue ? 'right' : 'left');
+ this.blockGraph.clear(changes.index.currentValue > changes.index.previousValue ? this.chainDirection : this.poolDirection);
}
this.isLoading$.next(true);
this.websocketService.startTrackMempoolBlock(changes.index.currentValue);
@@ -60,16 +74,17 @@ export class MempoolBlockOverviewComponent implements OnDestroy, OnChanges, Afte
ngOnDestroy(): void {
this.blockSub.unsubscribe();
this.deltaSub.unsubscribe();
+ this.timeLtrSubscription.unsubscribe();
this.websocketService.stopTrackMempoolBlock();
}
replaceBlock(transactionsStripped: TransactionStripped[]): void {
const blockMined = (this.stateService.latestBlockHeight > this.lastBlockHeight);
if (this.blockIndex !== this.index) {
- const direction = (this.blockIndex == null || this.index < this.blockIndex) ? 'left' : 'right';
+ const direction = (this.blockIndex == null || this.index < this.blockIndex) ? this.poolDirection : this.chainDirection;
this.blockGraph.enter(transactionsStripped, direction);
} else {
- this.blockGraph.replace(transactionsStripped, blockMined ? 'right' : 'left');
+ this.blockGraph.replace(transactionsStripped, blockMined ? this.chainDirection : this.poolDirection);
}
this.lastBlockHeight = this.stateService.latestBlockHeight;
@@ -81,10 +96,10 @@ export class MempoolBlockOverviewComponent implements OnDestroy, OnChanges, Afte
const blockMined = (this.stateService.latestBlockHeight > this.lastBlockHeight);
if (this.blockIndex !== this.index) {
- const direction = (this.blockIndex == null || this.index < this.blockIndex) ? 'left' : 'right';
+ const direction = (this.blockIndex == null || this.index < this.blockIndex) ? this.poolDirection : this.chainDirection;
this.blockGraph.replace(delta.added, direction);
} else {
- this.blockGraph.update(delta.added, delta.removed, blockMined ? 'right' : 'left', blockMined);
+ this.blockGraph.update(delta.added, delta.removed, blockMined ? this.chainDirection : this.poolDirection, blockMined);
}
this.lastBlockHeight = this.stateService.latestBlockHeight;
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 77ca95b2f..9e70c6e74 100644
--- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html
+++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html
@@ -1,5 +1,5 @@
-
+
@@ -45,7 +45,7 @@
-
+
diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss
index c41cde3fc..8032be92f 100644
--- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss
+++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss
@@ -1,7 +1,7 @@
.bitcoin-block {
width: 125px;
height: 125px;
- transition: 2s;
+ transition: background 2s, right 2s, transform 1s;
}
.block-size {
@@ -33,6 +33,7 @@
.block-body {
text-align: center;
+ transition: transform 1s;
}
@keyframes opacityPulse {
@@ -73,6 +74,7 @@
background-color: #232838;
transform:skew(40deg);
transform-origin:top;
+ transition: transform 1s, left 1s;
}
.bitcoin-block::before {
@@ -83,9 +85,11 @@
top: -12px;
left: -20px;
background-color: #191c27;
+ z-index: -1;
transform: skewY(50deg);
transform-origin: top;
+ transition: transform 1s, left 1s;
}
.mempool-block.bitcoin-block::after {
@@ -128,3 +132,18 @@
.blockLink:hover {
text-decoration: none;
}
+
+.time-ltr {
+ .bitcoin-block::after {
+ transform: skew(-40deg);
+ left: 20px;
+ }
+
+ .bitcoin-block::before {
+ transform: skewY(-50deg);
+ left: 125px;
+ }
+ .block-body {
+ transform: scaleX(-1);
+ }
+}
\ No newline at end of file
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 4202330b0..17236e2ca 100644
--- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts
+++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts
@@ -36,6 +36,8 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
now = new Date().getTime();
timeOffset = 0;
showMiningInfo = false;
+ timeLtrSubscription: Subscription;
+ timeLtr: boolean;
blockWidth = 125;
blockPadding = 30;
@@ -44,7 +46,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
feeRounding = '1.0-0';
rightPosition = 0;
- transition = '2s';
+ transition = 'background 2s, right 2s, transform 1s';
markIndex: number;
txFeePerVSize: number;
@@ -72,6 +74,11 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
this.location.onUrlChange((url) => this.enabledMiningInfoIfNeeded(url));
}
+ this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
+ this.timeLtr = !!ltr;
+ this.cd.markForCheck();
+ });
+
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
this.feeRounding = '1.0-1';
}
@@ -160,8 +167,10 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
if (this.markIndex === undefined) {
return;
}
+ const prevKey = this.timeLtr ? 'ArrowLeft' : 'ArrowRight';
+ const nextKey = this.timeLtr ? 'ArrowRight' : 'ArrowLeft';
- if (event.key === 'ArrowRight') {
+ if (event.key === prevKey) {
if (this.mempoolBlocks[this.markIndex - 1]) {
this.router.navigate([this.relativeUrlPipe.transform('mempool-block/'), this.markIndex - 1]);
} else {
@@ -173,7 +182,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
}
});
}
- } else if (event.key === 'ArrowLeft') {
+ } else if (event.key === nextKey) {
if (this.mempoolBlocks[this.markIndex + 1]) {
this.router.navigate([this.relativeUrlPipe.transform('/mempool-block/'), this.markIndex + 1]);
}
@@ -185,6 +194,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
this.markBlocksSubscription.unsubscribe();
this.blockSubscription.unsubscribe();
this.networkSubscription.unsubscribe();
+ this.timeLtrSubscription.unsubscribe();
clearTimeout(this.resetTransitionTimeout);
}
@@ -269,7 +279,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
this.arrowVisible = true;
this.resetTransitionTimeout = window.setTimeout(() => {
- this.transition = '2s';
+ this.transition = 'background 2s, right 2s, transform 1s';
this.cd.markForCheck();
}, 100);
return;
diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts
index b0e018941..920f32dd9 100644
--- a/frontend/src/app/services/state.service.ts
+++ b/frontend/src/app/services/state.service.ts
@@ -6,6 +6,7 @@ import { BlockExtended, DifficultyAdjustment, OptimizedMempoolStats } from '../i
import { Router, NavigationStart } from '@angular/router';
import { isPlatformBrowser } from '@angular/common';
import { map, shareReplay } from 'rxjs/operators';
+import { StorageService } from './storage.service';
interface MarkBlockState {
blockHeight?: number;
@@ -108,10 +109,12 @@ export class StateService {
keyNavigation$ = new Subject();
blockScrolling$: Subject = new Subject();
+ timeLtr: BehaviorSubject;
constructor(
@Inject(PLATFORM_ID) private platformId: any,
private router: Router,
+ private storageService: StorageService,
) {
const browserWindow = window || {};
// @ts-ignore
@@ -147,6 +150,11 @@ export class StateService {
}
this.blockVSize = this.env.BLOCK_WEIGHT_UNITS / 4;
+
+ this.timeLtr = new BehaviorSubject(this.storageService.getValue('time-preference-ltr') === 'true');
+ this.timeLtr.subscribe((ltr) => {
+ this.storageService.setValue('time-preference-ltr', ltr ? 'true' : 'false');
+ });
}
setNetworkBasedonUrl(url: string) {
diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts
index c4973d75c..dd06269d2 100644
--- a/frontend/src/app/shared/shared.module.ts
+++ b/frontend/src/app/shared/shared.module.ts
@@ -4,7 +4,7 @@ import { NgbCollapse, NgbCollapseModule, NgbRadioGroup, NgbTypeaheadModule } fro
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle,
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown,
- faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft } from '@fortawesome/free-solid-svg-icons';
+ faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate } from '@fortawesome/free-solid-svg-icons';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { MasterPageComponent } from '../components/master-page/master-page.component';
import { PreviewTitleComponent } from '../components/master-page-preview/preview-title.component';
@@ -291,6 +291,7 @@ export class SharedModule {
library.addIcons(faFileAlt);
library.addIcons(faRedoAlt);
library.addIcons(faArrowAltCircleRight);
+ library.addIcons(faArrowsRotate);
library.addIcons(faExternalLinkAlt);
library.addIcons(faSortUp);
library.addIcons(faCaretUp);