Merge pull request #782 from SonicWizard/ionic

Ionic updates; block height search and block detail
This commit is contained in:
Jason Dreyzehner 2017-07-29 01:18:11 -04:00 committed by GitHub
commit 8852924a14
19 changed files with 367 additions and 23 deletions

3
.gitignore vendored
View File

@ -15,6 +15,7 @@ results
build
node_modules
package-lock.json
# extras
*.swp
@ -47,3 +48,5 @@ public/css/main.css
README.html
po/*
!po/*.po
mynode/

View File

@ -37,11 +37,12 @@
"devDependencies": {
"@angular/cli": "1.1.2",
"@ionic/app-scripts": "1.3.7",
"@ionic/cli-plugin-cordova": "1.4.0",
"@ionic/cli-plugin-ionic-angular": "1.3.1",
"@ionic/cli-plugin-cordova": "1.5.0",
"@ionic/cli-plugin-ionic-angular": "1.4.0",
"@types/jasmine": "2.5.41",
"@types/node": "7.0.4",
"codecov": "2.2.0",
"ionic": "3.6.0",
"jasmine-core": "2.5.2",
"jasmine-spec-reporter": "3.2.0",
"karma": "1.4.1",

View File

@ -0,0 +1,48 @@
<div>
<div *ngFor="let tx of transactions.txs" style="border: 1px solid purple">
<p>{{ tx.txid }}</p>
<div [hidden]="!tx.firstSeenTs">
<span translate>first seen at</span>
<time>{{tx.firstSeenTs * 1000 | date:'medium'}}</time>
</div>
<div [hidden]="!tx.blocktime && tx.firstSeenTs">
<span translate>mined</span>
<time>{{tx.time * 1000 | date:'medium'}}</time>
</div>
<div [hidden]="!tx.isCoinBase">
<div *ngFor="let vin of tx.vin">
<div>
<span translate>No Inputs (Newly Generated Coins)</span>
</div>
</div>
<div>
&gt;
</div>
<div *ngFor="let vout of tx.vout">
<div>
<p>{{ getAddress(vout) }} {{ vout.value + ' BTC' }} <span [hidden]="!vout.spentTxId">(S)</span><span [hidden]="vout.spentTxId">(U)</span></p>
</div>
</div>
</div>
<div [hidden]="tx.isCoinBase">
<div *ngFor="let vin of tx.vin">
<div>{{ vin.addr }}</div>
<p>{{ vin.value + ' BTC' }}</p>
</div>
<div>
&gt;
</div>
<div *ngFor="let vout of tx.vout">
<div>
<p>{{ getAddress(vout) }} {{ vout.value + ' BTC' }} <span [hidden]="!vout.spentTxId">(S)</span><span [hidden]="vout.spentTxId">(U)</span></p>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TransactionsComponent } from './transactions';
@NgModule({
declarations: [
TransactionsComponent
],
imports: [
IonicModule
],
exports: [
TransactionsComponent
]
})
export class TransactionsComponentModule {}

View File

@ -0,0 +1,3 @@
transactions {
}

View File

@ -0,0 +1,50 @@
import { Component } from '@angular/core';
import { Input } from '@angular/core';
import { Http } from '@angular/http';
/**
* Generated class for the TransactionsComponent component.
*
* See https://angular.io/docs/ts/latest/api/core/index/ComponentMetadata-class.html
* for more info on Angular Components.
*/
@Component({
selector: 'transactions',
templateUrl: 'transactions.html'
})
export class TransactionsComponent {
public loading: boolean = true;
@Input() public blockHash: string;
public transactions: any = [];
constructor(private http: Http) {
}
private ngOnInit(): void {
let apiPrefix: string = 'http://localhost:3001/insight-api/';
this.http.get(apiPrefix + 'txs?block=' + this.blockHash).subscribe(
(data) => {
this.transactions = JSON.parse(data['_body']);
this.loading = false;
this.transactions.txs.forEach((tx) => {
console.log('tx is', tx);
});
},
(err) => {
console.log('err is', err);
this.loading = false;
}
);
}
public getAddress(vout: any): string {
if (vout.scriptPubKey && vout.scriptPubKey.addresses) {
return vout.scriptPubKey.addresses[0];
} else {
return 'Unparsed address';
}
}
}

View File

@ -0,0 +1,41 @@
<!--
Generated template for the BlockDetailPage page.
See http://ionicframework.com/docs/components/#navigation for more info on
Ionic pages and navigation.
-->
<ion-header>
<ion-navbar>
<ion-title>Block Detail</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<h1>Block # {{ block.height }}</h1>
<p>BlockHash {{ block.hash }}</p>
<h2>Summary</h2>
<div *ngIf="!loading">
<p>Number of Transactions {{ block.tx.length }}</p>
<p>Height {{ block.height }} <span [hidden]="!block.isMainChain">(Mainchain)</span></p>
<p>Block Reward {{ block.reward + ' BTC' }}</p>
<p>Timestamp {{ block.time * 1000 | date:'medium' }}</p>
<p>Mined by <a href="{{ block.poolInfo.url }}">{{ block.poolInfo.poolName }}</a></p>
<p>Merkle Root {{ block.merkleroot }}</p>
<p>Previous Block <a (click)="goToPreviousBlock()">{{ block.height - 1 }}</a></p>
<p>Difficulty {{ block.difficulty }}</p>
<p>Bits {{ block.bits }}</p>
<p>Size (bytes) {{ block.size }}</p>
<p>Version {{ block.version }}</p>
<p>Nonce {{ block.nonce }}</p>
<p>Next Block <a (click)="goToNextBlock()">{{ block.height + 1 }}</a></p>
<h3>Transactions</h3>
<transactions [blockHash]="block.hash"></transactions>
</div>
<div *ngIf="loading">
<p>Loading...</p>
</div>
</ion-content>

View File

@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { BlockDetailPage } from './block-detail';
import { TransactionsComponent } from '../../components/transactions/transactions';
@NgModule({
declarations: [
BlockDetailPage,
TransactionsComponent
],
imports: [
IonicPageModule.forChild(BlockDetailPage)
],
exports: [
BlockDetailPage
]
})
export class BlockDetailPageModule {}

View File

@ -0,0 +1,3 @@
page-block-detail {
}

View File

@ -0,0 +1,60 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { Http } from '@angular/http';
/**
* Generated class for the BlockDetailPage page.
*
* See http://ionicframework.com/docs/components/#navigation for more info
* on Ionic pages and navigation.
*/
@IonicPage({
name: 'block-detail',
segment: 'block/:blockHash'
})
@Component({
selector: 'page-block-detail',
templateUrl: 'block-detail.html'
})
export class BlockDetailPage {
public loading: boolean = true;
private blockHash: string;
public block: any = {
tx: []
};
constructor(public navCtrl: NavController, private http: Http, public navParams: NavParams) {
this.blockHash = navParams.get('blockHash');
let apiPrefix: string = 'http://localhost:3001/insight-api/';
this.http.get(apiPrefix + 'block/' + this.blockHash).subscribe(
(data) => {
this.block = JSON.parse(data['_body']);
this.loading = false;
},
(err) => {
console.log('err is', err);
this.loading = false;
}
);
}
public ionViewWillLeave(): void {
this.loading = true;
}
public goToPreviousBlock(): void {
this.navCtrl.push('block-detail', {
'blockHash': this.block.previousblockhash
});
}
public goToNextBlock(): void {
this.navCtrl.push('block-detail', {
'blockHash': this.block.nextblockhash
});
}
}

View File

@ -14,6 +14,9 @@
<ion-content padding>
<ion-list>
<ion-row>
<ion-searchbar (ionInput)="search($event)" placeholder="{{ 'Search for block, transaction or address' }}" [(ngModel)]="q"></ion-searchbar>
</ion-row>
<ion-item>
<ion-row>
<ion-col>Height</ion-col>

View File

@ -20,4 +20,10 @@ describe('Blocks', () => {
it('initializes', () => {
expect(instance).toBeTruthy();
});
it('has a search method', () => {
spyOn(instance, 'search');
instance.search();
expect(instance.search).toHaveBeenCalled();
});
});

View File

@ -3,6 +3,7 @@ import { NavController } from 'ionic-angular';
import { Observable } from 'rxjs';
import { Block } from '../../models';
import { BlocksService } from '../../services';
import { Http } from '@angular/http';
@Component({
templateUrl: './blocksPage.html'
@ -10,11 +11,13 @@ import { BlocksService } from '../../services';
export class BlocksPage {
public loading: boolean;
public title: string;
public blocks: Observable<Block[]>;
public q: string;
public badQuery: boolean = false;
constructor(private nav: NavController, private blocksService: BlocksService) {
this.nav = nav;
constructor(private navCtrl: NavController, private http: Http, private blocksService: BlocksService) {
this.title = 'Blocks';
this.blocks = blocksService.latestBlocks;
// this.blocks.subscribe((blocks) => {
@ -22,4 +25,67 @@ export class BlocksPage {
// });
blocksService.getLatestBlocks();
}
public search(): void {
console.log('q is', this.q);
let apiPrefix: string = 'http://localhost:3001/insight-api/';
this.http.get(apiPrefix + 'block/' + this.q).subscribe(
(data) => {
this.resetSearch();
console.log('block', data);
},
() => {
this.http.get(apiPrefix + 'tx/' + this.q).subscribe(
(data: any) => {
this.resetSearch();
console.log('tx', data);
},
() => {
this.http.get(apiPrefix + 'addr/' + this.q).subscribe(
(data: any) => {
this.resetSearch();
console.log('addr', data);
},
() => {
this.http.get(apiPrefix + 'block-index/' + this.q).subscribe(
function (data: any): void {
this.resetSearch();
let parsedData: any = JSON.parse(data._body);
this.navCtrl.push('block-detail', {
'blockHash': parsedData.blockHash
});
}.bind(this),
function (): void {
this.loading = false;
this.reportBadQuery();
}.bind(this)
);
}
);
}
);
}
);
}
private resetSearch(): void {
this.q = '';
this.loading = false;
}
/* tslint:disable:no-unused-variable */
private reportBadQuery(): void {
this.badQuery = true;
console.log('badQuery', this.badQuery);
setTimeout(
function (): void {
this.badQuery = false;
console.log('badQuery', this.badQuery);
}.bind(this),
2000
);
};
/* tslint:enable:no-unused-variable */
}

View File

@ -8,18 +8,24 @@
</ion-header>
<ion-content padding>
<ion-list>
<ion-item>
<p>This form can be used to broadcast a raw transaction in hex format over the Bitcoin network.</p>
</ion-item>
<form [formGroup]="txForm">
<ion-list>
<ion-item>
<p>This form can be used to broadcast a raw transaction in hex format over the Bitcoin network.</p>
</ion-item>
<ion-item>
<ion-label floating>Raw transaction data</ion-label>
<ion-input type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Raw transaction data</ion-label>
<ion-input type="text" [(ngModel)]="transaction" formControlName="rawData"></ion-input>
</ion-item>
<ion-item>
<button ion-button outline>Send transaction</button>
</ion-item>
</ion-list>
<ion-item *ngIf="!txForm.controls.rawData.valid">
<p>Raw transaction data must be a valid hexadecimal string.</p>
</ion-item>
<ion-item>
<button ion-button outline (click)="send()" [disabled]="!txForm.touched || !txForm.valid">Send transaction</button>
</ion-item>
</ion-list>
</form>
</ion-content>

View File

@ -20,4 +20,10 @@ describe('BroadcastTxPage', () => {
it('initializes', () => {
expect(instance).toBeTruthy();
});
it('has a send method', () => {
spyOn(instance, 'send');
instance.send();
expect(instance.send).toHaveBeenCalled();
});
});

View File

@ -1,5 +1,6 @@
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
templateUrl: './broadcastTxPage.html'
@ -9,9 +10,22 @@ export class BroadcastTxPage {
public title: string;
private nav: NavController;
public transaction: string;
public txForm: FormGroup;
constructor(nav: NavController) {
constructor(nav: NavController, public formBuilder: FormBuilder) {
this.nav = nav;
this.title = 'Broadcast Transaction';
this.txForm = formBuilder.group({
rawData: ['', Validators.pattern(/^[0-9A-Fa-f]+$/)]
});
}
public send(): void {
let postData: any = {
rawtx: this.transaction
};
console.log('the postData is', postData);
}
}

View File

@ -1,8 +1,8 @@
import { BlocksService } from './blocksService';
import { Block } from '../models';
import { TestUtils } from '../test';
// import { BlocksService } from './blocksService';
// import { Block } from '../models';
// import { TestUtils } from '../test';
let blocks: BlocksService = null;
// let blocks: BlocksService = null;
// describe('BlocksService', () => {

View File

@ -1,6 +1,6 @@
import { Http, Response } from '@angular/http';
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { Subject } from 'rxjs';
import { Block, InsightBlockObject } from '../models';
@Injectable()

View File

@ -11,7 +11,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { getTestBed, TestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
import { HttpModule } from '@angular/http';
import { MockBackend } from '@angular/http/testing';
// import { MockBackend } from '@angular/http/testing';
import { App, Config, Form, IonicModule, Keyboard, DomController, MenuController, NavController, Platform, GestureController } from 'ionic-angular';
import { ConfigMock, PlatformMock } from './mocks';
import { BlocksServiceMock } from './services/mocks';