Unverified Commit 214e01ee authored by Jeremy Bokobza's avatar Jeremy Bokobza Committed by GitHub

Merge pull request #388 from herbepau/advanced

Advanced
parents 944f2161 9938d6a2
......@@ -12,7 +12,7 @@
"breeze",
"ui",
"stratis",
"bitcoin",
"bitcoin",
"angular",
"electron",
"typescript",
......@@ -58,13 +58,13 @@
"core-js": "2.5.3",
"electron-context-menu": "0.9.1",
"enhanced-resolve": "3.4.1",
"ngx-clipboard": "9.0.0",
"ngx-bootstrap": "2.0.0-beta.11",
"ngx-clipboard": "9.0.0",
"rxjs": "5.5.5",
"zone.js": "0.8.18"
},
"devDependencies": {
"@angular/cli": "1.6.0",
"@angular/cli": "^1.7.4",
"@angular/compiler-cli": "5.1.0",
"@angular/language-service": "5.1.0",
"@types/core-js": "0.9.36",
......@@ -73,8 +73,8 @@
"autoprefixer": "7.2.2",
"circular-dependency-plugin": "4.3.0",
"codelyzer": "4.0.2",
"copyfiles": "1.2.0",
"copy-webpack-plugin": "4.2.3",
"copyfiles": "1.2.0",
"cross-env": "5.1.1",
"css-loader": "0.28.7",
"cssnano": "3.10.0",
......@@ -101,10 +101,11 @@
"less-loader": "4.0.5",
"minimist": "1.2.0",
"mkdirp": "0.5.1",
"node-sass": "4.7.2",
"npm-run-all": "4.1.2",
"npx": "9.7.1",
"node-sass": "4.7.2",
"popper.js": "1.12.9",
"postcss-custom-properties": "^7.0.0",
"postcss-loader": "2.0.9",
"postcss-url": "7.3.0",
"protractor": "5.2.1",
......
......@@ -17,6 +17,7 @@ import { GenericModalComponent } from './shared/components/generic-modal/generic
import { ApiService } from './shared/services/api.service';
import { GlobalService } from './shared/services/global.service';
import { ModalService } from './shared/services/modal.service';
import { NavigationService } from './shared/services/navigation.service';
import { SendComponent } from './wallet/send/send.component';
import { SendConfirmationComponent } from './wallet/send/send-confirmation/send-confirmation.component';
......@@ -55,7 +56,7 @@ import { LogoutConfirmationComponent } from './wallet/logout-confirmation/logout
TransactionDetailsComponent,
LogoutConfirmationComponent
],
providers: [ ApiService, GlobalService, ModalService, Title ],
providers: [ NavigationService, ApiService, GlobalService, ModalService, Title ],
bootstrap: [ AppComponent ]
})
......
import { Subscription } from 'rxJs/Subscription';
export class SerialDisposable {
private subscription: Subscription;
set disposable(value: Subscription) {
this.dispose();
this.subscription = value;
}
dispose() {
if (this.subscription) {
this.subscription.unsubscribe();
this.subscription = null;
}
}
}
import { Injectable } from '@angular/core';
import { Router, Event, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operator/filter';
import { map } from 'rxjs/operator/map';
import { ReplaySubject } from 'rxJs/ReplaySubject';
export enum Page {
Bitcoin, Stratis
}
@Injectable()
export class NavigationService {
private readonly navBase = '/wallet';
constructor(router: Router) {
const navigation$ = router.events.filter(x => x instanceof NavigationEnd)
.map(x => <NavigationEnd>x)
.map(x => x.url);
navigation$.filter(x => x === this.navBase).subscribe(_ => this.pageSubject.next(Page.Bitcoin));
navigation$.filter(x => x === `${this.navBase}/stratis-wallet`).subscribe(_ => this.pageSubject.next(Page.Stratis));
}
public pageSubject = new ReplaySubject(1);
}
.mainDiv {
margin-left: 100px;
margin-top: 20px;
}
::ng-deep ng-datepicker input {
height: 35px;
}
.label {
font-size: 14px;
margin-bottom: 1px;
}
\ No newline at end of file
<div class="mainDiv">
<form [formGroup]='icoFormGroup'>
<div style="margin-top:20px">
<div>
<label class="label">Extended Public Key</label>
<div class="myAddress" *ngIf="extPubKeyLoadingState.success; else elseFeedback">
<code style="overflow-wrap: break-word">{{ extPubKey }}</code>
</div>
<ng-template #elseFeedback>
<app-feedback [loading]="extPubKeyLoadingState.loading" [errored]="extPubKeyLoadingState.errored" [erroredText]="'Failed to get Extended Public Key'"></app-feedback>
</ng-template>
</div>
<div style="display: flex; flex-direction: row; margin-top:30px">
<div style="width:275px">
<label class="label">Generate Addresses</label>
<div style="display:flex; flex-direction:row; height:34px">
<input formControlName="addressCountControl" type="text" class="form-control" placeholder="Number to generate..." style="border-radius: 0px; width: 163px">
<button *ngIf="!addressCountControl.invalid && addressCount" [disabled]="addressCountControl.invalid || !addressCount" (click)="generateAddresses()"
class="btn btn-darkgray btn-sm" type="button" style="margin-left: 5px">Go</button>
<app-feedback style="margin-left:5px; width:115px" [loading]="generateAddressesLoadingState.loading" [success]="showAddressesTick"
[errored]="generateAddressesLoadingState.errored" [erroredText]="'Failed'" [showSuccessTick]="true"></app-feedback>
</div>
</div>
<div style="margin-left: 30px">
<label class="label">Resync</label>
<div style="display:flex; flex-direction:row">
<ngb-datepicker [(ngModel)]="resyncDate" [outsideDays]="hidden" [minDate]="minResyncDate" [maxDate]="maxResyncDate" style="outline:none; border:none"
formControlName="datePickerControl"></ngb-datepicker>
<button [disabled]="!resyncDate" style="margin-left: 5px; align-self:flex-start; height:34px" (click)="resync()" class="btn btn-darkgray btn-sm"
type="button">Go</button>
<app-feedback style="margin-left:5px; width:65px" [loading]="resyncLoadingState.loading" [showSuccessTick]="true" [errored]="resyncLoadingState.errored"
[erroredText]="'Failed'" [success]="showResyncTick"></app-feedback>
</div>
</div>
</div>
</div>
</form>
</div>
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AdvancedComponent } from './advanced.component';
describe('AdvancedComponent', () => {
let component: AdvancedComponent;
let fixture: ComponentFixture<AdvancedComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AdvancedComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AdvancedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, Validators, FormBuilder, AbstractControl } from '@angular/forms';
import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
import 'rxjs/add/operator/filter';
import './monitor';
import { AdvancedService } from './advanced.service';
import { LoadingState } from './loadingState';
import { SerialDisposable } from '../../../app/shared/classes/serialDisposable';
@Component({
selector: 'app-advanced',
templateUrl: './advanced.component.html',
styleUrls: ['./advanced.component.css']
})
export class AdvancedComponent implements OnInit, OnDestroy {
private addressCount = '';
private extPubKeySubs = new SerialDisposable();
private generateAddressesSubs = new SerialDisposable();
private resyncSubs = new SerialDisposable();
private addresses: string[] = [];
private resyncActioned = false;
constructor(readonly advancedService: AdvancedService, readonly formBuilder: FormBuilder) {
this.setResyncDates();
}
icoFormGroup: FormGroup;
extPubKey = '';
extPubKeyLoadingState = new LoadingState();
generateAddressesLoadingState = new LoadingState();
resyncLoadingState = new LoadingState();
resyncDate: NgbDateStruct;
maxResyncDate: NgbDateStruct;
minResyncDate: NgbDateStruct;
get datePickerControl(): AbstractControl { return this.icoFormGroup.get('datePickerControl'); }
get addressCountControl(): AbstractControl { return this.icoFormGroup.get('addressCountControl'); }
get showAddressesTick(): boolean { return this.generateAddressesLoadingState.success &&
this.addresses.length && (Number(this.addressCount)===this.addresses.length); }
get showResyncTick(): boolean { return this.resyncLoadingState.success && this.resyncActioned; }
ngOnInit() {
this.registerFormControls();
this.loadExtPubKey();
}
resync() {
this.resyncActioned = true;
const date = new Date(this.resyncDate.year, this.resyncDate.month-1, this.resyncDate.day);
this.resyncSubs.disposable = this.advancedService.resyncFromDate(date)
.monitor(this.resyncLoadingState)
.subscribe();
}
generateAddresses() {
this.addresses = [];
this.generateAddressesSubs.disposable = this.advancedService.generateAddresses(Number(this.addressCount))
.monitor(this.generateAddressesLoadingState)
.subscribe(x => this.addresses = x);
}
private loadExtPubKey() {
this.extPubKeySubs.disposable = this.advancedService.getExtPubKey()
.monitor(this.extPubKeyLoadingState)
.subscribe(x => this.extPubKey = x);
}
private registerFormControls() {
this.icoFormGroup = this.formBuilder.group({
addressCountControl: ['', [Validators.pattern('^[1-9][0-9]*$')]],
datePickerControl: [Validators.required]
});
const control = this.addressCountControl;
const changes$ = control.valueChanges;
changes$.filter(_ => control.invalid).subscribe(_ => control.setValue(this.addressCount));
changes$.filter(_ => !control.invalid).subscribe(_ => this.addressCount = control.value);
}
private setResyncDates() {
const now = new Date();
this.maxResyncDate = { year: now.getFullYear(), month: now.getMonth()+1, day: now.getDate() }
this.minResyncDate = { year: now.getFullYear(), month: 1, day: 1 }
}
ngOnDestroy() {
this.extPubKeySubs.dispose();
this.generateAddressesSubs.dispose();
this.resyncSubs.dispose();
}
}
import { TestBed, inject } from '@angular/core/testing';
import { AdvancedService } from './advanced.service';
describe('AdvancedService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AdvancedService]
});
});
it('should be created', inject([AdvancedService], (service: AdvancedService) => {
expect(service).toBeTruthy();
}));
});
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import 'rxjs/add/observable/empty';
import { GlobalService } from '../../shared/services/global.service';
import { NavigationService, Page } from '../../shared/services/navigation.service';
class dateRequest {
constructor(public date: Date){}
}
@Injectable()
export class AdvancedService {
private urlPrefix = '';
private readonly walletName;
private readonly accountName = 'account 0';
private headers = new HttpHeaders({'Content-Type': 'application/json'});
constructor(private httpClient: HttpClient, private globalService: GlobalService, navigationService: NavigationService) {
this.walletName = this.globalService.getWalletName();
navigationService.pageSubject.subscribe(x =>
this.urlPrefix = `http://localhost:3722${Page.Bitcoin ? 0 : 1}/api/Wallet/`);
}
public getExtPubKey(): Observable<string> {
const url = `${this.urlPrefix}extpubkey?WalletName=${this.walletName}&AccountName=${this.accountName}`;
return this.httpClient.get(url).map(x => x.toString());
}
public generateAddresses(count: number): Observable<string[]> {
const url = `${this.urlPrefix}unusedaddresses?WalletName=${this.walletName}&AccountName=${this.accountName}&Count=${count}`;
return this.httpClient.get(url).map(x => this.processAddresses(x));
}
public resyncFromDate(date: Date): Observable<any> {
date = new Date(date.getFullYear(), date.getMonth(), date.getDate()); //<- Strip any time values
const url = `${this.urlPrefix}syncfromdate`;
const data = JSON.stringify(new dateRequest(date));
return this.httpClient.post(url, data, {headers: this.headers}).map((x: Response) => x);
}
private processAddresses(response: any): string[] {
let addresses = new Array<string>();
for (const address of response) {
addresses.push(address);
}
return addresses;
}
}
.img {
width:16px;
height:16px;
margin-left:3px;
}
\ No newline at end of file
<div>
<div class="progress" *ngIf="loading">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0"
aria-valuemax="100" style="width: 100%">
<label style="font-size:10px">Working...</label>
</div>
</div>
<div *ngIf="errored">
<img class="img" src="../../../../assets/images/if_Error_16.png" />
</div>
<div *ngIf="success">
<img class="img" src="../../../../assets/images/Tick_Mark-16.png" />
</div>
</div>
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FeedbackComponent } from './feedback.component';
describe('FeedbackComponent', () => {
let component: FeedbackComponent;
let fixture: ComponentFixture<FeedbackComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FeedbackComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FeedbackComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-feedback',
templateUrl: './feedback.component.html',
styleUrls: ['./feedback.component.css']
})
export class FeedbackComponent {
private _loading = false;
private _errored = false;
private _erroredText = "Failed";
private _success = false;
private _showSuccessTick = false;
@Input()
public set loading(value: boolean) {
this._loading = value;
}
public get loading(): boolean {
return this._loading;
}
@Input()
public set errored(value: boolean) {
this._errored = value;
}
public get errored(): boolean {
return this._errored;
}
@Input()
public set erroredText(value: string) {
this._erroredText = value;
}
public get erroredText(): string {
return this._erroredText;
}
@Input()
public set success(value: boolean) {
this._success = value;
}
public get success(): boolean {
return this._success;
}
@Input()
public set showSuccessTick(value: boolean) {
this._showSuccessTick = value;
}
public get showSuccessTick(): boolean {
return this._showSuccessTick;
}
}
export class LoadingState {
private _loading = false;
private _errored = false;
private _erroredText = "";
public get erroredText(): string {
return this._erroredText;
}
public set erroredText(value: string) {
this._erroredText = value;
}
public get loading(): boolean {
return this._loading;
}
public set loading(value: boolean) {
this._loading = value;
if (this._loading) {
this._errored = false;
}
}
public get errored(): boolean {
return this._errored;
}
public set errored(value: boolean) {
this._errored = value;
if (this._errored) {
this._loading = false;
}
}
public get success(): boolean {
return !this._loading && !this._errored;
}
}
import { Observable } from 'rxjs/Observable';
import { LoadingState } from './loadingState';
function monitor<T>(this: Observable<T>, loadingState: LoadingState): Observable<T> {
return Observable.create(observer => {
loadingState.loading = true;
loadingState.errored = false;
const subs = this.subscribe(x => {
loadingState.loading = false;
observer.next(x);
}, e => {
loadingState.loading = false;
loadingState.errored = true;
observer.error(e);
}, () => {
loadingState.loading = false;
observer.complete();
});
return () => subs.unsubscribe();
});
};
Observable.prototype.monitor = monitor;
declare module 'rxjs/Observable' {
interface Observable<T> {
monitor: typeof monitor;
}
}
......@@ -11,7 +11,6 @@ import { SendComponent } from '../send/send.component';
import { ReceiveComponent } from '../receive/receive.component';
import { TransactionDetailsComponent } from '../transaction-details/transaction-details.component';
import { Observable } from 'rxjs/Rx';
import { Subscription } from 'rxjs/Subscription';
@Component({
......@@ -96,8 +95,9 @@ export class DashboardComponent implements OnInit {
.subscribe(
response => {
if (response.status >= 200 && response.status < 400) {
if (response.json().transactionsHistory.length > 0) {
historyResponse = response.json().transactionsHistory;
const json = response.json();
if (json.transactionsHistory) {
historyResponse = json.transactionsHistory;
this.getTransactionInfo(historyResponse);
}
}
......
......@@ -8,7 +8,6 @@ import { ModalService } from '../../shared/services/modal.service';
import { WalletInfo } from '../../shared/classes/wallet-info';
import { TransactionInfo } from '../../shared/classes/transaction-info';
import { Observable } from 'rxjs/Rx';
import { Subscription } from 'rxjs/Subscription';
import { TransactionDetailsComponent } from '../transaction-details/transaction-details.component';
......@@ -49,8 +48,9 @@ export class HistoryComponent {
.subscribe(
response => {
if (response.status >= 200 && response.status < 400) {
if (response.json().transactionsHistory.length > 0) {
historyResponse = response.json().transactionsHistory;
const json = response.json();
if (json && json.transactionsHistory) {
historyResponse = json.transactionsHistory;
this.getTransactionInfo(historyResponse);
}
}
......
......@@ -8,6 +8,9 @@
<li class="nav-item">
<a class="nav-link" routerLink="history" [routerLinkActive]="['is-active']">History</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="advanced" [routerLinkActive]="['is-active']">Advanced</a>
</li>
</ul>
<!-- /ul-->
<div class="ml-auto d-flex align-items-center">
......
......@@ -6,6 +6,7 @@ import { Router } from '@angular/router';
import { ApiService } from '../../shared/services/api.service';
import { GlobalService } from '../../shared/services/global.service';
import { ModalService } from '../../shared/services/modal.service';
import { NavigationService } from '../../shared/services/navigation.service';
import { WalletInfo } from '../../shared/classes/wallet-info';
......@@ -16,7 +17,9 @@ import { WalletInfo } from '../../shared/classes/wallet-info';
})
export class SidebarComponent implements OnInit {
constructor(private globalService: GlobalService, private apiService: ApiService, private router: Router, private modalService: NgbModal, private genericModalService: ModalService) { }
constructor(private globalService: GlobalService, private apiService: ApiService,
private router: Router, private modalService: NgbModal, private genericModalService: ModalService,
private navigationService: NavigationService) { }
public bitcoinActive: boolean;
public stratisActive: boolean;
......
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { Subscription } from 'rxjs/Subscription';
import { ApiService } from '../../shared/services/api.service';
......
......@@ -2,24 +2,25 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { WalletComponent } from './wallet.component';
import { SendComponent } from './send/send.component';
import { ReceiveComponent } from './receive/receive.component';
import { HistoryComponent } from './history/history.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { AdvancedComponent } from './advanced/advanced.component'
const routes: Routes = [
{ path: '', component: WalletComponent,
children: [
{ path: '', redirectTo:'dashboard', pathMatch:'full' },
{ path: 'dashboard', component: DashboardComponent},
{ path: 'history', component: HistoryComponent}
{ path: 'dashboard', component: DashboardComponent },
{ path: 'history', component: HistoryComponent },
{ path: 'advanced', component: AdvancedComponent }
]
},
{ path: 'stratis-wallet', component: WalletComponent,
children: [
{ path: '', redirectTo:'dashboard', pathMatch:'full' },
{ path: 'dashboard', component: DashboardComponent},
{ path: 'history', component: HistoryComponent}
{ path: 'dashboard', component: DashboardComponent },
{ path: 'history', component: HistoryComponent },
{ path: 'advanced', component: AdvancedComponent }
]
}
];
......
......@@ -3,6 +3,8 @@ import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { ClipboardModule } from 'ngx-clipboard';
import { HttpClientModule } from '@angular/common/http';
import { NgDatepickerModule } from 'ng2-datepicker';
import { WalletComponent } from './wallet.component';
import { MenuComponent } from './menu/menu.component';
......@@ -13,17 +15,22 @@ import { SharedModule } from '../shared/shared.module';
import { WalletRoutingModule } from './wallet-routing.module';
import { SidebarComponent } from './sidebar/sidebar.component';
import { StatusBarComponent } from './status-bar/status-bar.component';
import { TransactionDetailsComponent } from './transaction-details/transaction-details.component';
import { AdvancedComponent } from './advanced/advanced.component';
import { AdvancedService } from '../wallet/advanced/advanced.service';
import { FeedbackComponent } from './advanced/feedback/feedback.component';
@NgModule({
imports: [
HttpClientModule,
CommonModule,
ClipboardModule,
FormsModule,
SharedModule.forRoot(),
NgbModule,
ReactiveFormsModule,
WalletRoutingModule
WalletRoutingModule,
NgDatepickerModule
],
declarations: [
WalletComponent,
......@@ -31,7 +38,12 @@ import { TransactionDetailsComponent } from './transaction-details/transaction-d
DashboardComponent,
HistoryComponent,
SidebarComponent,
StatusBarComponent
StatusBarComponent,
AdvancedComponent,
FeedbackComponent
],
providers: [
AdvancedService
],
exports: []
})
......
......@@ -238,7 +238,7 @@ function getPlugins() {
}
module.exports = {
"devtool": "source-map",
"devtool": "eval-source-map",
"externals": {
"electron": "require('electron')",
"buffer": "require('buffer')",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment