diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts
index 6f435c80a..76da3254d 100644
--- a/frontend/src/app/app-routing.module.ts
+++ b/frontend/src/app/app-routing.module.ts
@@ -3,30 +3,42 @@ import { Routes, RouterModule } from '@angular/router';
import { BlockchainComponent } from './blockchain/blockchain.component';
import { AboutComponent } from './about/about.component';
import { StatisticsComponent } from './statistics/statistics.component';
+import { TelevisionComponent } from './television/television.component';
+import { MasterPageComponent } from './master-page/master-page.component';
const routes: Routes = [
{
path: '',
- children: [],
- component: BlockchainComponent
+ component: MasterPageComponent,
+ children: [
+ {
+ path: '',
+ children: [],
+ component: BlockchainComponent
+ },
+ {
+ path: 'tx/:id',
+ children: [],
+ component: BlockchainComponent
+ },
+ {
+ path: 'about',
+ children: [],
+ component: AboutComponent
+ },
+ {
+ path: 'statistics',
+ component: StatisticsComponent,
+ },
+ {
+ path: 'graphs',
+ component: StatisticsComponent,
+ },
+ ],
},
{
- path: 'tx/:id',
- children: [],
- component: BlockchainComponent
- },
- {
- path: 'about',
- children: [],
- component: AboutComponent
- },
- {
- path: 'statistics',
- component: StatisticsComponent,
- },
- {
- path: 'graphs',
- component: StatisticsComponent,
+ path: 'tv',
+ component: TelevisionComponent,
},
{
path: '**',
diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html
index aca28bbea..90c6b6463 100644
--- a/frontend/src/app/app.component.html
+++ b/frontend/src/app/app.component.html
@@ -1,32 +1 @@
-
-
-
-
\ No newline at end of file
diff --git a/frontend/src/app/app.component.scss b/frontend/src/app/app.component.scss
index bdaf25149..e69de29bb 100644
--- a/frontend/src/app/app.component.scss
+++ b/frontend/src/app/app.component.scss
@@ -1,28 +0,0 @@
-li.nav-item.active {
- background-color: #653b9c;
-}
-
-li.nav-item {
- padding: 10px;
-}
-
-.navbar {
- z-index: 100;
-}
-
-@media (min-width: 768px) {
- .navbar {
- padding: 0rem 1rem;
- }
- li.nav-item {
- padding: 20px;
- }
-}
-
-.logo {
- margin-left: 40px;
-}
-
-li.nav-item a {
- color: #ffffff;
-}
diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts
index 98ad9de95..d198fcdfa 100644
--- a/frontend/src/app/app.component.ts
+++ b/frontend/src/app/app.component.ts
@@ -1,52 +1,10 @@
-import { Component, OnInit } from '@angular/core';
-import { MemPoolService } from './services/mem-pool.service';
-import { Router } from '@angular/router';
-import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
-export class AppComponent implements OnInit {
- navCollapsed = false;
- isOffline = false;
- searchForm: FormGroup;
-
- constructor(
- private memPoolService: MemPoolService,
- private router: Router,
- private formBuilder: FormBuilder,
- ) { }
-
- ngOnInit() {
- this.searchForm = this.formBuilder.group({
- txId: ['', Validators.pattern('^[a-fA-F0-9]{64}$')],
- });
-
- this.memPoolService.isOffline$
- .subscribe((state) => {
- this.isOffline = state;
- });
- }
-
- collapse(): void {
- this.navCollapsed = !this.navCollapsed;
- }
-
- search() {
- const txId = this.searchForm.value.txId;
- if (txId) {
- if (window.location.pathname === '/' || window.location.pathname.substr(0, 4) === '/tx/') {
- window.history.pushState({}, '', `/tx/${txId}`);
- } else {
- this.router.navigate(['/tx/', txId]);
- }
- this.memPoolService.txIdSearch$.next(txId);
- this.searchForm.setValue({
- txId: '',
- });
- this.collapse();
- }
- }
+export class AppComponent {
+ constructor() { }
}
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index 70d488ef2..8431e5ef6 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -14,8 +14,11 @@ import { ReactiveFormsModule } from '@angular/forms';
import { BlockModalComponent } from './blockchain-blocks/block-modal/block-modal.component';
import { StatisticsComponent } from './statistics/statistics.component';
import { ProjectedBlockModalComponent } from './blockchain-projected-blocks/projected-block-modal/projected-block-modal.component';
+import { TelevisionComponent } from './television/television.component';
import { BlockchainBlocksComponent } from './blockchain-blocks/blockchain-blocks.component';
import { BlockchainProjectedBlocksComponent } from './blockchain-projected-blocks/blockchain-projected-blocks.component';
+import { ApiService } from './services/api.service';
+import { MasterPageComponent } from './master-page/master-page.component';
@NgModule({
declarations: [
@@ -27,8 +30,10 @@ import { BlockchainProjectedBlocksComponent } from './blockchain-projected-block
TxBubbleComponent,
BlockModalComponent,
ProjectedBlockModalComponent,
+ TelevisionComponent,
BlockchainBlocksComponent,
BlockchainProjectedBlocksComponent,
+ MasterPageComponent,
],
imports: [
ReactiveFormsModule,
@@ -38,6 +43,7 @@ import { BlockchainProjectedBlocksComponent } from './blockchain-projected-block
SharedModule,
],
providers: [
+ ApiService,
MemPoolService,
],
entryComponents: [
diff --git a/frontend/src/app/blockchain-blocks/blockchain-blocks.component.ts b/frontend/src/app/blockchain-blocks/blockchain-blocks.component.ts
index aef5b4c7e..3dc52ed66 100644
--- a/frontend/src/app/blockchain-blocks/blockchain-blocks.component.ts
+++ b/frontend/src/app/blockchain-blocks/blockchain-blocks.component.ts
@@ -21,7 +21,10 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
ngOnInit() {
this.blocksSubscription = this.memPoolService.blocks$
- .subscribe((block) => this.blocks.unshift(block));
+ .subscribe((block) => {
+ this.blocks.unshift(block);
+ this.blocks = this.blocks.slice(0, 8);
+ });
}
ngOnDestroy() {
diff --git a/frontend/src/app/master-page/master-page.component.html b/frontend/src/app/master-page/master-page.component.html
new file mode 100644
index 000000000..aeba9e2bc
--- /dev/null
+++ b/frontend/src/app/master-page/master-page.component.html
@@ -0,0 +1,32 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/master-page/master-page.component.scss b/frontend/src/app/master-page/master-page.component.scss
new file mode 100644
index 000000000..bdaf25149
--- /dev/null
+++ b/frontend/src/app/master-page/master-page.component.scss
@@ -0,0 +1,28 @@
+li.nav-item.active {
+ background-color: #653b9c;
+}
+
+li.nav-item {
+ padding: 10px;
+}
+
+.navbar {
+ z-index: 100;
+}
+
+@media (min-width: 768px) {
+ .navbar {
+ padding: 0rem 1rem;
+ }
+ li.nav-item {
+ padding: 20px;
+ }
+}
+
+.logo {
+ margin-left: 40px;
+}
+
+li.nav-item a {
+ color: #ffffff;
+}
diff --git a/frontend/src/app/master-page/master-page.component.ts b/frontend/src/app/master-page/master-page.component.ts
new file mode 100644
index 000000000..ac9368ac6
--- /dev/null
+++ b/frontend/src/app/master-page/master-page.component.ts
@@ -0,0 +1,54 @@
+import { Component, OnInit } from '@angular/core';
+import { MemPoolService } from '../services/mem-pool.service';
+import { Router } from '@angular/router';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+
+@Component({
+ selector: 'app-master-page',
+ templateUrl: './master-page.component.html',
+ styleUrls: ['./master-page.component.scss']
+})
+export class MasterPageComponent implements OnInit {
+
+ navCollapsed = false;
+ isOffline = false;
+ searchForm: FormGroup;
+
+ constructor(
+ private memPoolService: MemPoolService,
+ private router: Router,
+ private formBuilder: FormBuilder,
+ ) { }
+
+ ngOnInit() {
+ this.searchForm = this.formBuilder.group({
+ txId: ['', Validators.pattern('^[a-fA-F0-9]{64}$')],
+ });
+
+ this.memPoolService.isOffline$
+ .subscribe((state) => {
+ this.isOffline = state;
+ });
+ }
+
+ collapse(): void {
+ this.navCollapsed = !this.navCollapsed;
+ }
+
+ search() {
+ const txId = this.searchForm.value.txId;
+ if (txId) {
+ if (window.location.pathname === '/' || window.location.pathname.substr(0, 4) === '/tx/') {
+ window.history.pushState({}, '', `/tx/${txId}`);
+ } else {
+ this.router.navigate(['/tx/', txId]);
+ }
+ this.memPoolService.txIdSearch$.next(txId);
+ this.searchForm.setValue({
+ txId: '',
+ });
+ this.collapse();
+ }
+ }
+
+}
diff --git a/frontend/src/app/statistics/statistics.component.ts b/frontend/src/app/statistics/statistics.component.ts
index 1f0aca0da..de1d953d3 100644
--- a/frontend/src/app/statistics/statistics.component.ts
+++ b/frontend/src/app/statistics/statistics.component.ts
@@ -215,13 +215,6 @@ export class StatisticsComponent implements OnInit {
};
}
- getTimeToNextTenMinutes(): number {
- const now = new Date();
- const nextInterval = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(),
- Math.floor(now.getMinutes() / 10) * 10 + 10, 0, 0);
- return nextInterval.getTime() - now.getTime();
- }
-
generateArray(mempoolStats: IMempoolStats[]) {
const logFees = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
diff --git a/frontend/src/app/television/television.component.html b/frontend/src/app/television/television.component.html
new file mode 100644
index 000000000..2b1db4c31
--- /dev/null
+++ b/frontend/src/app/television/television.component.html
@@ -0,0 +1,25 @@
+
\ No newline at end of file
diff --git a/frontend/src/app/television/television.component.scss b/frontend/src/app/television/television.component.scss
new file mode 100644
index 000000000..b510145c3
--- /dev/null
+++ b/frontend/src/app/television/television.component.scss
@@ -0,0 +1,35 @@
+#tv-wrapper {
+ height: 100%;
+ margin: 10px;
+ margin-top: 20px;
+}
+
+.blockchain-wrapper {
+ overflow: hidden;
+}
+
+.position-container {
+ position: absolute;
+ left: 50%;
+ bottom: 150px;
+}
+
+#divider {
+ width: 3px;
+ height: 175px;
+ left: 0;
+ top: -40px;
+ background-image: url('/assets/divider-new.png');
+ background-repeat: repeat-y;
+ position: absolute;
+}
+
+#divider > img {
+ position: absolute;
+ left: -100px;
+ top: -28px;
+}
+
+.chart-holder {
+ height: calc(100% - 220px);
+}
\ No newline at end of file
diff --git a/frontend/src/app/television/television.component.ts b/frontend/src/app/television/television.component.ts
new file mode 100644
index 000000000..4c3ee43ff
--- /dev/null
+++ b/frontend/src/app/television/television.component.ts
@@ -0,0 +1,118 @@
+import { Component, OnInit, LOCALE_ID, Inject } from '@angular/core';
+import { ApiService } from '../services/api.service';
+import { formatDate } from '@angular/common';
+import { BytesPipe } from '../shared/pipes/bytes-pipe/bytes.pipe';
+
+import * as Chartist from 'chartist';
+import { IMempoolStats } from '../blockchain/interfaces';
+import { MemPoolService } from '../services/mem-pool.service';
+
+@Component({
+ selector: 'app-television',
+ templateUrl: './television.component.html',
+ styleUrls: ['./television.component.scss']
+})
+export class TelevisionComponent implements OnInit {
+ loading = true;
+
+ mempoolStats: IMempoolStats[] = [];
+ mempoolVsizeFeesData: any;
+ mempoolVsizeFeesOptions: any;
+
+ constructor(
+ private apiService: ApiService,
+ @Inject(LOCALE_ID) private locale: string,
+ private bytesPipe: BytesPipe,
+ private memPoolService: MemPoolService,
+ ) { }
+
+ ngOnInit() {
+ this.apiService.sendWebSocket({'action': 'want', data: ['projected-blocks', 'live-2h-chart']});
+
+ const labelInterpolationFnc = (value: any, index: any) => {
+ return index % 6 === 0 ? formatDate(value, 'HH:mm', this.locale) : null;
+ };
+
+ this.mempoolVsizeFeesOptions = {
+ showArea: true,
+ showLine: false,
+ fullWidth: true,
+ showPoint: false,
+ low: 0,
+ axisX: {
+ labelInterpolationFnc: labelInterpolationFnc,
+ offset: 40
+ },
+ axisY: {
+ labelInterpolationFnc: (value: number): any => {
+ return this.bytesPipe.transform(value);
+ },
+ offset: 50
+ },
+ plugins: [
+ Chartist.plugins.ctTargetLine({
+ value: 1000000
+ }),
+ ]
+ };
+
+ this.apiService.list2HStatistics$()
+ .subscribe((mempoolStats) => {
+ this.mempoolStats = mempoolStats;
+ this.handleNewMempoolData(this.mempoolStats.concat([]));
+ this.loading = false;
+ });
+
+ this.memPoolService.live2Chart$
+ .subscribe((mempoolStats) => {
+ this.mempoolStats.unshift(mempoolStats);
+ this.mempoolStats = this.mempoolStats.slice(0, this.mempoolStats.length - 1);
+ this.handleNewMempoolData(this.mempoolStats.concat([]));
+ });
+ }
+
+ handleNewMempoolData(mempoolStats: IMempoolStats[]) {
+ mempoolStats.reverse();
+ const labels = mempoolStats.map(stats => stats.added);
+
+ const finalArrayVbyte = this.generateArray(mempoolStats);
+
+ // Remove the 0-1 fee vbyte since it's practially empty
+ finalArrayVbyte.shift();
+
+ this.mempoolVsizeFeesData = {
+ labels: labels,
+ series: finalArrayVbyte
+ };
+ }
+
+ generateArray(mempoolStats: IMempoolStats[]) {
+ const logFees = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
+ 250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
+
+ logFees.reverse();
+
+ const finalArray: number[][] = [];
+ let feesArray: number[] = [];
+
+ logFees.forEach((fee) => {
+ feesArray = [];
+ mempoolStats.forEach((stats) => {
+ // @ts-ignore
+ const theFee = stats['vsize_' + fee];
+ if (theFee) {
+ feesArray.push(parseInt(theFee, 10));
+ } else {
+ feesArray.push(0);
+ }
+ });
+ if (finalArray.length) {
+ feesArray = feesArray.map((value, i) => value + finalArray[finalArray.length - 1][i]);
+ }
+ finalArray.push(feesArray);
+ });
+ finalArray.reverse();
+ return finalArray;
+ }
+
+}
diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss
index 129ca1c2e..deec4d859 100644
--- a/frontend/src/styles.scss
+++ b/frontend/src/styles.scss
@@ -66,6 +66,10 @@ body {
margin-bottom: 60px;
}
+html, body {
+ height: 100%;
+}
+
@media (min-width: 768px) {
body.disable-scroll {
overflow: hidden;