diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts
index 77c397ed0..dce48d646 100644
--- a/frontend/src/app/app-routing.module.ts
+++ b/frontend/src/app/app-routing.module.ts
@@ -13,6 +13,7 @@ import { AssetComponent } from './components/asset/asset.component';
import { AssetsComponent } from './assets/assets.component';
import { StatusViewComponent } from './components/status-view/status-view.component';
import { DashboardComponent } from './dashboard/dashboard.component';
+import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component';
const routes: Routes = [
{
@@ -41,6 +42,10 @@ const routes: Routes = [
},
],
},
+ {
+ path: 'blocks',
+ component: LatestBlocksComponent,
+ },
{
path: 'graphs',
component: StatisticsComponent,
@@ -85,6 +90,10 @@ const routes: Routes = [
},
],
},
+ {
+ path: 'blocks',
+ component: LatestBlocksComponent,
+ },
{
path: 'graphs',
component: StatisticsComponent,
@@ -150,6 +159,10 @@ const routes: Routes = [
},
],
},
+ {
+ path: 'blocks',
+ component: LatestBlocksComponent,
+ },
{
path: 'graphs',
component: StatisticsComponent,
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index fe5952f4f..b4562bde8 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -16,6 +16,7 @@ import { StateService } from './services/state.service';
import { BlockComponent } from './components/block/block.component';
import { AddressComponent } from './components/address/address.component';
import { SearchFormComponent } from './components/search-form/search-form.component';
+import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component';
import { WebsocketService } from './services/websocket.service';
import { AddressLabelsComponent } from './components/address-labels/address-labels.component';
import { MempoolBlocksComponent } from './components/mempool-blocks/mempool-blocks.component';
@@ -41,6 +42,8 @@ import { SharedModule } from './shared/shared.module';
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 { faChartArea, faCube, faDatabase, faInfo, faInfoCircle, faList, faQuestion, faQuestionCircle, faTachometerAlt, faThList, faTv } from '@fortawesome/free-solid-svg-icons';
@NgModule({
declarations: [
@@ -57,6 +60,7 @@ import { DashboardComponent } from './dashboard/dashboard.component';
TransactionsListComponent,
AddressComponent,
AmountComponent,
+ LatestBlocksComponent,
SearchFormComponent,
TimespanComponent,
AddressLabelsComponent,
@@ -80,6 +84,7 @@ import { DashboardComponent } from './dashboard/dashboard.component';
BrowserAnimationsModule,
InfiniteScrollModule,
NgbTypeaheadModule,
+ FontAwesomeModule,
SharedModule,
],
providers: [
@@ -91,4 +96,15 @@ import { DashboardComponent } from './dashboard/dashboard.component';
],
bootstrap: [AppComponent]
})
-export class AppModule { }
+export class AppModule {
+ constructor(library: FaIconLibrary) {
+ library.addIcons(faInfoCircle);
+ library.addIcons(faChartArea);
+ library.addIcons(faTv);
+ library.addIcons(faCube);
+ library.addIcons(faThList);
+ library.addIcons(faList);
+ library.addIcons(faTachometerAlt);
+ library.addIcons(faDatabase);
+ }
+}
diff --git a/frontend/src/app/components/latest-blocks/latest-blocks.component.html b/frontend/src/app/components/latest-blocks/latest-blocks.component.html
new file mode 100644
index 000000000..e6cafe69d
--- /dev/null
+++ b/frontend/src/app/components/latest-blocks/latest-blocks.component.html
@@ -0,0 +1,40 @@
+
+
Blocks
+
+
+
+
+
+
+ Height |
+ Timestamp |
+ Mined |
+ Transactions |
+ Filled |
+
+
+
+ {{ block.height }} |
+ {{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} |
+ ago |
+ {{ block.tx_count | number }} |
+
+
+
+ {{ block.size | bytes: 2 }}
+
+ |
+
+
+
+ |
+ |
+ |
+ |
+ |
+
+
+
+
+
+
diff --git a/frontend/src/app/components/latest-blocks/latest-blocks.component.scss b/frontend/src/app/components/latest-blocks/latest-blocks.component.scss
new file mode 100644
index 000000000..0f2246c99
--- /dev/null
+++ b/frontend/src/app/components/latest-blocks/latest-blocks.component.scss
@@ -0,0 +1,14 @@
+.progress {
+ background-color: #2d3348;
+}
+
+@media (min-width: 768px) {
+ .d-md-block {
+ display: table-cell !important;
+ }
+}
+@media (min-width: 992px) {
+ .d-lg-block {
+ display: table-cell !important;
+ }
+}
diff --git a/frontend/src/app/components/latest-blocks/latest-blocks.component.ts b/frontend/src/app/components/latest-blocks/latest-blocks.component.ts
new file mode 100644
index 000000000..c773c91b9
--- /dev/null
+++ b/frontend/src/app/components/latest-blocks/latest-blocks.component.ts
@@ -0,0 +1,115 @@
+import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
+import { ElectrsApiService } from '../../services/electrs-api.service';
+import { StateService } from '../../services/state.service';
+import { Block } from '../../interfaces/electrs.interface';
+import { Subscription, Observable, merge, of } from 'rxjs';
+import { SeoService } from '../../services/seo.service';
+import { WebsocketService } from 'src/app/services/websocket.service';
+
+@Component({
+ selector: 'app-latest-blocks',
+ templateUrl: './latest-blocks.component.html',
+ styleUrls: ['./latest-blocks.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class LatestBlocksComponent implements OnInit, OnDestroy {
+ network$: Observable;
+
+ blocks: any[] = [];
+ blockSubscription: Subscription;
+ isLoading = true;
+ interval: any;
+
+ latestBlockHeight: number;
+
+ heightOfPageUntilBlocks = 150;
+ heightOfBlocksTableChunk = 470;
+
+ constructor(
+ private electrsApiService: ElectrsApiService,
+ private stateService: StateService,
+ private seoService: SeoService,
+ private websocketService: WebsocketService,
+ private cd: ChangeDetectorRef,
+ ) { }
+
+ ngOnInit() {
+ this.seoService.resetTitle();
+ this.websocketService.want(['blocks']);
+
+ this.network$ = merge(of(''), this.stateService.networkChanged$);
+
+ this.blockSubscription = this.stateService.blocks$
+ .subscribe(([block]) => {
+ if (block === null || !this.blocks.length) {
+ return;
+ }
+
+ this.latestBlockHeight = block.height;
+
+ if (block.height === this.blocks[0].height) {
+ return;
+ }
+
+ // If we are out of sync, reload the blocks instead
+ if (block.height > this.blocks[0].height + 1) {
+ this.loadInitialBlocks();
+ return;
+ }
+
+ if (block.height <= this.blocks[0].height) {
+ return;
+ }
+
+ this.blocks.pop();
+ this.blocks.unshift(block);
+ this.cd.markForCheck();
+ });
+
+ this.loadInitialBlocks();
+ }
+
+ ngOnDestroy() {
+ clearInterval(this.interval);
+ this.blockSubscription.unsubscribe();
+ }
+
+ loadInitialBlocks() {
+ this.electrsApiService.listBlocks$()
+ .subscribe((blocks) => {
+ this.blocks = blocks;
+ this.isLoading = false;
+
+ this.latestBlockHeight = blocks[0].height;
+
+ const spaceForBlocks = window.innerHeight - this.heightOfPageUntilBlocks;
+ const chunks = Math.ceil(spaceForBlocks / this.heightOfBlocksTableChunk) - 1;
+ if (chunks > 0) {
+ this.loadMore(chunks);
+ }
+ this.cd.markForCheck();
+ });
+ }
+
+ loadMore(chunks = 0) {
+ if (this.isLoading) {
+ return;
+ }
+ this.isLoading = true;
+ this.electrsApiService.listBlocks$(this.blocks[this.blocks.length - 1].height - 1)
+ .subscribe((blocks) => {
+ this.blocks = this.blocks.concat(blocks);
+ this.isLoading = false;
+
+ const chunksLeft = chunks - 1;
+ if (chunksLeft > 0) {
+ this.loadMore(chunksLeft);
+ }
+ this.cd.markForCheck();
+ });
+ }
+
+ trackByBlock(index: number, block: Block) {
+ return block.height;
+ }
+}
diff --git a/frontend/src/app/components/master-page/master-page.component.html b/frontend/src/app/components/master-page/master-page.component.html
index b51c5da5b..5e01662c5 100644
--- a/frontend/src/app/components/master-page/master-page.component.html
+++ b/frontend/src/app/components/master-page/master-page.component.html
@@ -28,31 +28,37 @@
-
+
diff --git a/frontend/src/app/components/master-page/master-page.component.scss b/frontend/src/app/components/master-page/master-page.component.scss
index 2be014e42..ed0395a1c 100644
--- a/frontend/src/app/components/master-page/master-page.component.scss
+++ b/frontend/src/app/components/master-page/master-page.component.scss
@@ -24,6 +24,9 @@ li.nav-item a {
color: #ffffff;
}
+.navbar-nav {
+ flex-direction: row;
+}
nav {
box-shadow: 0px 0px 15px 0px #000;
diff --git a/frontend/src/app/components/search-form/search-form.component.html b/frontend/src/app/components/search-form/search-form.component.html
index 9f5e850dc..024b80744 100644
--- a/frontend/src/app/components/search-form/search-form.component.html
+++ b/frontend/src/app/components/search-form/search-form.component.html
@@ -1,6 +1,6 @@
-