Merge pull request #782 from SonicWizard/ionic
Ionic updates; block height search and block detail
This commit is contained in:
commit
8852924a14
3
.gitignore
vendored
3
.gitignore
vendored
@ -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/
|
||||
|
||||
@ -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",
|
||||
|
||||
48
app/src/components/transactions/transactions.html
Normal file
48
app/src/components/transactions/transactions.html
Normal 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>
|
||||
>
|
||||
</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>
|
||||
>
|
||||
</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>
|
||||
16
app/src/components/transactions/transactions.module.ts
Normal file
16
app/src/components/transactions/transactions.module.ts
Normal 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 {}
|
||||
3
app/src/components/transactions/transactions.scss
Normal file
3
app/src/components/transactions/transactions.scss
Normal file
@ -0,0 +1,3 @@
|
||||
transactions {
|
||||
|
||||
}
|
||||
50
app/src/components/transactions/transactions.ts
Normal file
50
app/src/components/transactions/transactions.ts
Normal 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';
|
||||
}
|
||||
}
|
||||
}
|
||||
41
app/src/pages/block-detail/block-detail.html
Normal file
41
app/src/pages/block-detail/block-detail.html
Normal 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>
|
||||
18
app/src/pages/block-detail/block-detail.module.ts
Normal file
18
app/src/pages/block-detail/block-detail.module.ts
Normal 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 {}
|
||||
3
app/src/pages/block-detail/block-detail.scss
Normal file
3
app/src/pages/block-detail/block-detail.scss
Normal file
@ -0,0 +1,3 @@
|
||||
page-block-detail {
|
||||
|
||||
}
|
||||
60
app/src/pages/block-detail/block-detail.ts
Normal file
60
app/src/pages/block-detail/block-detail.ts
Normal 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
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@ -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 */
|
||||
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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', () => {
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user