Merge pull request #69 from gloriousCode/master

GoCryptoTraderWeb phase 2
This commit is contained in:
Adrian Gallagher
2017-12-15 16:14:21 +11:00
committed by GitHub
97 changed files with 12430 additions and 10113 deletions

View File

@@ -1,5 +1,5 @@
# Cryptocurrency trading bot written in Golang
![Snazzy logo](/src/assets/page-logo.png "Now with snazzy logo")
[![Build Status](https://travis-ci.org/thrasher-/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-/gocryptotrader)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-/gocryptotrader)
@@ -86,7 +86,7 @@ Make any neccessary changes to the config file.
Run the application!
## Donations
![Snazzy logo](/web/src/assets/early-dumb-donate.png "Now with snazzy logo")
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: 1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB
## Binaries

View File

@@ -1,4 +1,5 @@
## GoCryptoTrader website
![Snazzy logo](/src/assets/page-logo.png "Optional title")
A website interface to interact with the main GoCryptoTrader application. It is developed with Angular 4 with support for Electron
## This is still in active development

20379
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
{
"name": "gocryptotrader-web",
"version": "0.1.0",
"version": "0.2.0",
"description": "Front-end interface for GoCryptoTrader",
"homepage": "https://github.com/thrasher-/gocryptotrader",
"homepage": "https://github.com/thrasher-/gocryptotrader/web",
"author": {
"name": "Scott",
"email": "scott@gloriousedge.com",
@@ -46,25 +46,28 @@
"e2e": "protractor ./protractor.conf.js"
},
"dependencies": {
"@angular/animations": "^4.3.4",
"@angular/cdk": "^2.0.0-beta.10",
"@angular/common": "4.3.0",
"@angular/compiler": "4.3.0",
"@angular/core": "4.3.4",
"@angular/forms": "4.3.0",
"@angular/http": "4.3.0",
"@angular/material": "^2.0.0-beta.10",
"@angular/platform-browser": "4.3.0",
"@angular/platform-browser-dynamic": "4.3.0",
"@angular/router": "4.3.0",
"@angular/animations": "5.0.2",
"@angular/cdk": "5.0.0-rc0",
"@angular/common": "5.0.2",
"@angular/compiler": "5.0.2",
"@angular/core": "5.0.2",
"@angular/forms": "5.0.2",
"@angular/http": "5.0.2",
"@angular/material": "^5.0.0-rc0",
"@angular/platform-browser": "5.0.2",
"@angular/platform-browser-dynamic": "5.0.2",
"@angular/router": "5.0.2",
"@ngtools/webpack": "1.8.2",
"core-js": "2.4.1",
"enhanced-resolve": "3.3.0",
"rxjs": "^5.4.3",
"extract-text-webpack-plugin": "^3.0.1",
"html-webpack-plugin": "^2.30.1",
"rxjs": "^5.5.0",
"zone.js": "0.8.12"
},
"devDependencies": {
"@angular/cli": "1.2.1",
"@angular/compiler-cli": "4.3.0",
"@angular/cli": "^1.5.2",
"@angular/compiler-cli": "5.0.2",
"@types/bluebird": "3.5.8",
"@types/core-js": "0.9.36",
"@types/jasmine": "2.5.53",
@@ -107,11 +110,11 @@
"stylus-loader": "3.0.1",
"ts-node": "3.1.0",
"tslint": "5.4.3",
"typescript": "2.4.1",
"typescript": "2.4.2",
"url-loader": "0.5.9",
"webdriver-manager": "12.0.6",
"webpack": "3.3.0",
"webpack-dev-server": "2.5.0"
"webpack": "3.8.1",
"webpack-dev-server": "2.9.4"
},
"license": "SEE LICENSE IN LICENSE.md"
}

View File

@@ -2,6 +2,12 @@ import { HomeComponent } from './pages/home/home.component';
import { SettingsComponent } from './pages/settings/settings.component';
import { AboutComponent } from './pages/about/about.component';
import { DashboardComponent } from './pages/dashboard/dashboard.component';
import { WalletComponent } from './pages/wallet/wallet.component';
import { DonateComponent } from './pages/donate/donate.component';
import { HistoryComponent } from './pages/history/history.component';
import { TradingComponent } from './pages/trading/trading.component';
import { ExchangeGridComponent } from './pages/exchange-grid/exchange-grid.component';
import { CurrencyListComponent } from './pages/currency-list/currency-list.component';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
@@ -9,7 +15,7 @@ import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{
path: '',
component: DashboardComponent
component: HomeComponent
},
{
path:'about',
@@ -22,6 +28,31 @@ const routes: Routes = [
{
path: 'settings',
component: SettingsComponent
},
{
path: 'wallet',
component: WalletComponent
}
,
{
path: 'donate',
component: DonateComponent
},
{
path: 'history',
component: HistoryComponent
},
{
path: 'trading',
component: TradingComponent
},
{
path: 'exchange-grid',
component: ExchangeGridComponent
},
{
path: 'currency-list',
component: CurrencyListComponent
}
];

View File

@@ -1,9 +1,58 @@
<app-navbar></app-navbar>
<app-navbar class="navbar mat-elevation-z6"></app-navbar>
<div class="sidebar"></div>
<div class="main">
<div class="main-content">
<router-outlet></router-outlet>
</div>
</div>
<mat-sidenav-container class="container ">
<mat-sidenav #sidenav mode="side" class="sidebar" opened="true">
<mat-nav-list>
<mat-list-item routerLink="dashboard" routerLinkActive="dashboard-highlight">
<mat-icon>view_quilt</mat-icon>&nbsp;
<h3>Dashboard</h3>
</mat-list-item>
<mat-list-item routerLink="wallet" routerLinkActive="wallet-highlight">
<mat-icon>account_balance_wallet</mat-icon>&nbsp;
<h3>Wallet</h3>
</mat-list-item>
<mat-list-item routerLink="trading" routerLinkActive="trading-highlight">
<mat-icon>swap_horiz</mat-icon>&nbsp;
<h3>Trading</h3>
</mat-list-item>
<mat-list-item routerLink="history" routerLinkActive="history-highlight">
<mat-icon>history</mat-icon>&nbsp;
<h3>History</h3>
</mat-list-item>
<mat-list-item routerLink="settings" routerLinkActive="settings-highlight">
<mat-icon>settings</mat-icon>&nbsp;
<h3>Settings</h3>
</mat-list-item>
<mat-list-item routerLink="donate" routerLinkActive="donate-highlight">
<mat-icon>thumb_up</mat-icon>&nbsp;
<h3>Donate</h3>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<mat-icon>grade</mat-icon>&nbsp;
<a href="https://github.com/thrasher-/gocryptotrader" target="_blank">
<h3>GitHub</h3>
</a>
</mat-list-item>
<mat-list-item>
<mat-icon>view_agenda</mat-icon>&nbsp;
<a href="https://trello.com/b/ZAhMhpOy/gocryptotrader" target="_blank">
<h3>Trello</h3>
</a>
</mat-list-item>
<mat-list-item>
<mat-icon>apps</mat-icon>&nbsp;
<a href="https://gocryptotrader.herokuapp.com/" target="_blank">
<h3>Slack</h3>
</a>
</mat-list-item>
<mat-list-item>
<mat-icon>bug_report</mat-icon>&nbsp;
<a href="https://github.com/thrasher-/gocryptotrader/issues/new" target="_blank">
<h3>Report a bug</h3>
</a>
</mat-list-item>
</mat-nav-list>
</mat-sidenav>
<router-outlet class="main"></router-outlet>
</mat-sidenav-container>

View File

@@ -0,0 +1,51 @@
.container {
height: 100vh;
min-height:100%;
}
.main {
display: flex;
align-items: center;
justify-content: center;
padding: 20px 70px 0;
min-width: 86%;
margin: 10px auto;
overflow-x: hidden;
margin-top:4rem;
}
.sidebar {
width: 14%;
position: fixed;
margin-top: 4rem;
}
.navbar {
position: fixed;
width: 100%;
z-index: 2;
}
.dashboard-highlight {
color: green !important;
}
.trading-highlight {
color: orangered !important;
}
.history-highlight {
color: cornflowerblue !important;
}
.wallet-highlight {
color: blueviolet !important;
}
.settings-highlight {
color: magenta !important;
}
.donate-highlight {
color: goldenrod!important;
}

View File

@@ -1,5 +1,8 @@
import { Component } from '@angular/core';
import { Component, OnInit,ViewChild } from '@angular/core';
import { ElectronService } from './providers/electron.service';
import { MatSidenav } from '@angular/material';
import { SidebarService } from './services/sidebar/sidebar.service';
import { Router, NavigationEnd } from '@angular/router';
@Component({
selector: 'app-root',
@@ -7,7 +10,11 @@ import { ElectronService } from './providers/electron.service';
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
constructor(public electronService: ElectronService) {
sidebarService: SidebarService
public currentUrl:string;
@ViewChild('sidenav') public sidenav: MatSidenav;
constructor(public electronService: ElectronService,something: SidebarService, private router:Router) {
if (electronService.isElectron()) {
console.log('Mode electron');
@@ -18,5 +25,19 @@ export class AppComponent {
} else {
console.log('Mode web');
}
this.sidebarService = something;
router.events.subscribe(event => {
if (event instanceof NavigationEnd ) {
console.log("current url",event.url); // event.url has current url
this.currentUrl = event.url;
}
});
}
ngOnInit() {
this.sidebarService.setSidenav(this.sidenav);
}
}

View File

@@ -9,16 +9,23 @@ import { NgModule, Injectable } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
MdButtonModule,
MdCardModule,
MdMenuModule,
MdToolbarModule,
MdIconModule,
MdFormFieldModule,
MdInputModule,
MdCheckboxModule,
MdGridListModule,
MdProgressSpinnerModule,
MatButtonModule,
MatCardModule,
MatMenuModule,
MatToolbarModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
MatCheckboxModule,
MatGridListModule,
MatProgressSpinnerModule,
MatSidenavModule,
MatListModule,
MatSortModule,
MatExpansionModule,
MatLineModule,
MatTooltipModule,
MatTabsModule,
} from '@angular/material';
@@ -27,18 +34,40 @@ import { HomeComponent } from './pages/home/home.component';
import { AboutComponent } from './pages/about/about.component';
import { SettingsComponent } from './pages/settings/settings.component';
import { DashboardComponent } from './pages/dashboard/dashboard.component';
import { WalletComponent } from './pages/wallet/wallet.component';
import { DonateComponent } from './pages/donate/donate.component';
//Shared
import { NavbarComponent } from './shared/navbar/navbar.component';
import { ExchangeCurrencyTickerComponent } from './shared/exchange-currency-ticker/exchange-currency-ticker.component';
import { AllEnabledCurrencyTickersComponent } from './shared/all-enabled-currency-tickers/all-enabled-currency-tickers.component';
import { ThemePickerComponent } from './shared/theme-picker/theme-picker';
//services
import { WebsocketService } from './services/websocket/websocket.service';
import { WebsocketHandlerService } from './services/websocket-handler/websocket-handler.service';
import { SidebarService } from './services/sidebar/sidebar.service';
import { ElectronService } from './providers/electron.service';
import { StyleManagerService } from './services/style-manager/style-manager.service';
import { ThemeStorageService } from './services/theme-storage/theme-storage.service';
//Routing
import { AppRoutingModule } from './app-routing.module';
import { Wallet } from './shared/classes/wallet';
import * as Rx from 'rxjs/Rx';
import { TradeHistoryComponent } from './shared/trade-history/trade-history.component';
import { PriceHistoryComponent } from './shared/price-history/price-history.component';
import { MyOrdersComponent } from './shared/my-orders/my-orders.component';
import { OrdersComponent } from './shared/orders/orders.component';
import { BuySellComponent } from './shared/buy-sell/buy-sell.component';
import { SelectedCurrencyComponent } from './shared/selected-currency/selected-currency.component';
import { TradingComponent } from './pages/trading/trading.component';
import { HistoryComponent } from './pages/history/history.component';
import { BuySellFormComponent } from './shared/buy-sell-form/buy-sell-form.component';
import { ExchangeGridComponent } from './pages/exchange-grid/exchange-grid.component';
import { CurrencyListComponent } from './pages/currency-list/currency-list.component';
@NgModule({
@@ -50,7 +79,21 @@ import * as Rx from 'rxjs/Rx';
SettingsComponent,
DashboardComponent,
ExchangeCurrencyTickerComponent,
AllEnabledCurrencyTickersComponent
AllEnabledCurrencyTickersComponent,
WalletComponent,
ThemePickerComponent,
TradeHistoryComponent,
PriceHistoryComponent,
MyOrdersComponent,
OrdersComponent,
BuySellComponent,
DonateComponent,
SelectedCurrencyComponent,
TradingComponent,
HistoryComponent,
BuySellFormComponent,
ExchangeGridComponent,
CurrencyListComponent,
],
imports: [
BrowserModule,
@@ -58,18 +101,32 @@ import * as Rx from 'rxjs/Rx';
HttpModule,
AppRoutingModule,
BrowserAnimationsModule,
MdButtonModule,
MdMenuModule,
MdCardModule,
MdToolbarModule,
MdIconModule,
MdFormFieldModule,
MdInputModule,
MdCheckboxModule,
MdGridListModule,
MdProgressSpinnerModule,
MatButtonModule,
MatMenuModule,
MatCardModule,
MatToolbarModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
MatCheckboxModule,
MatGridListModule,
MatProgressSpinnerModule,
MatSidenavModule,
MatListModule,
MatSortModule,
MatExpansionModule,
MatLineModule,
MatTooltipModule,
MatTabsModule,
],
providers: [
ElectronService,
WebsocketService,
WebsocketHandlerService,
SidebarService,
StyleManagerService,
ThemeStorageService,
],
providers: [ElectronService,WebsocketService,WebsocketHandlerService],
bootstrap: [AppComponent]
})
export class AppModule {

View File

@@ -0,0 +1,35 @@
<mat-list>
<h3 matSubheader>Poloniex</h3>
<mat-list-item >
<mat-icon matListIcon>attach_money</mat-icon>
<h4 matLine>BTC_USD</h4>
<button mat-button>SELECT</button>
</mat-list-item>
<mat-list-item >
<mat-icon matListIcon>attach_money</mat-icon>
<h4 matLine>LTC_USD</h4>
<button mat-button>SELECT</button>
</mat-list-item>
<mat-list-item >
<mat-icon matListIcon>attach_money</mat-icon>
<h4 matLine>ETH_USD</h4>
<button mat-button>SELECT</button>
</mat-list-item>
<mat-divider></mat-divider>
<h3 matSubheader>Kraken</h3>
<mat-list-item >
<mat-icon matListIcon>attach_money</mat-icon>
<h4 matLine>BTC_USD</h4>
<button mat-button>SELECT</button>
</mat-list-item>
<mat-list-item >
<mat-icon matListIcon>attach_money</mat-icon>
<h4 matLine>LTC_USD</h4>
<button mat-button>SELECT</button>
</mat-list-item>
<mat-list-item >
<mat-icon matListIcon>attach_money</mat-icon>
<h4 matLine>ETH_USD</h4>
<button mat-button>SELECT</button>
</mat-list-item>
</mat-list>

View File

@@ -0,0 +1,9 @@
.BTC {
color:orange;
}
.LTC {
color:silver;
}
.ETH {
color:darkslategrey;
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CurrencyListComponent } from './currency-list.component';
describe('CurrencyListComponent', () => {
let component: CurrencyListComponent;
let fixture: ComponentFixture<CurrencyListComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CurrencyListComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CurrencyListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-currency-list',
templateUrl: './currency-list.component.html',
styleUrls: ['./currency-list.component.scss']
})
export class CurrencyListComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@@ -1 +1,22 @@
<app-all-enabled-currency-tickers></app-all-enabled-currency-tickers>
<button matTooltip="Trade" mat-fab color="accent" class="mat-fab mat-fab-bottom-right"><mat-icon >swap_horiz</mat-icon></button>
<ng-template app-buy-sell></ng-template>
<div style="width:97%;">
<mat-grid-list cols="3" rowHeight="24rem">
<mat-grid-tile [colspan]="tile.columns" [rowspan]="tile.rows" *ngFor="let tile of dashboard.tiles">
<mat-card class="full-card">
<mat-card-header>
<mat-card-title>{{tile.title}}</mat-card-title>
<mat-card-subtitle>{{tile.subTitle}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
{{tile.content}}
</mat-card-content>
<mat-card-footer>
<button *ngIf="!expanded" (click)="expandTile(tile)" mat-button>EXPAND</button>
<button *ngIf="expanded" (click)="resetTiles()" mat-button>RESTORE</button>
</mat-card-footer>
</mat-card>
</mat-grid-tile>
</mat-grid-list>
</div>

View File

@@ -0,0 +1,23 @@
.full-card {
margin: 10px;
width: 100%;
}
::ng-deep mat-grid-tile.mat-grid-tile .mat-figure {
align-items: initial;
/*vertical alignment*/
}
.mat-card-footer {
position: absolute;
bottom: 24px;
}
.mat-fab {
top: auto;
right: 30px;
bottom: 20px;
left: auto;
position: fixed;
z-index: 3;
}

View File

@@ -1,18 +1,65 @@
import { Component, OnInit } from '@angular/core';
import {Component, OnInit }from '@angular/core';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'],
@Component( {
selector:'app-dashboard',
templateUrl:'./dashboard.component.html',
styleUrls:['./dashboard.component.scss'],
})
export class DashboardComponent implements OnInit {
constructor()
{
}
public dashboard:any;
public expanded:boolean = false;
ngOnInit() {
constructor() {
}
ngOnInit() {
this.resetTiles();
}
public expandTile(tile:any) {
for(var i = 0; i< this.dashboard.tiles.length; i++) {
if(this.dashboard.tiles[i].title === tile.title ) {
this.dashboard.tiles[i].rows = 2;
this.dashboard.tiles[i].columns = 3;
this.expanded = true;
} else {
this.dashboard.tiles[i].rows = 0;
this.dashboard.tiles[i].columns = 0;
}
}
}
public resetTiles() {
this.expanded = false;
this.dashboard = {tiles:[ {
title:'Trade History:',
subTitle:'Trade History',
content:'<app-trade-history></app-trade-history>',
columns:1,
rows:2,
}, {
title:'Price History:',
subTitle:'Price History',
content:'<app-price-history></app-price-history>',
columns:2,
rows:1,
}, {
title:'My Orders:',
subTitle:'My Orders',
content:'<app-my-orders></app-my-orders>',
columns:1,
rows:1,
}, {
title:'Orders:',
subTitle:'Orders',
content:'<app-orders></app-orders>',
columns:1,
rows:1,
},
]};
}
}

View File

@@ -0,0 +1,17 @@
<mat-card class="full-card">
<mat-card-header>
<mat-card-title>Donations</mat-card-title>
<mat-card-subtitle>We give our thanks</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<img class="heart" src="/assets/early-dumb-donate.png"/>
<p>If this framework helped you in any way, or you would like to support the developers working on it, please donate</p>
<mat-list>
<mat-list-item >
<mat-icon mat-list-icon class="BTC">attach_money</mat-icon>
<h4 mat-line>Address:</h4>
<h4 mat-line>1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB</h4>
</mat-list-item>
</mat-list>
</mat-card-content>
</mat-card>

View File

@@ -0,0 +1,13 @@
.BTC {
color:orange;
}
.full-card {
width: 30%;
margin: 0px auto !important;
}
.heart {
margin: 0px auto !important;
display:flex;
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DonateComponent } from './donate.component';
describe('DonateComponent', () => {
let component: DonateComponent;
let fixture: ComponentFixture<DonateComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DonateComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DonateComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-donate',
templateUrl: './donate.component.html',
styleUrls: ['./donate.component.scss']
})
export class DonateComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@@ -0,0 +1,60 @@
<mat-accordion>
<mat-expansion-panel >
<mat-expansion-panel-header>
<mat-panel-title>
Poloniex
</mat-panel-title>
<mat-panel-description>
</mat-panel-description>
</mat-expansion-panel-header>
image and blurb
<form class="form-content">
<mat-list>
<mat-list-item >
<mat-icon matListIcon>attach_money</mat-icon>
<h4 matLine>BTC_USD</h4>
<button mat-button>SELECT</button>
</mat-list-item>
<mat-list-item >
<mat-icon matListIcon>attach_money</mat-icon>
<h4 matLine>LTC_USD</h4>
<button mat-button>SELECT</button>
</mat-list-item>
<mat-list-item >
<mat-icon matListIcon>attach_money</mat-icon>
<h4 matLine>ETH_USD</h4>
<button mat-button>SELECT</button>
</mat-list-item>
</mat-list>
</form>
</mat-expansion-panel><mat-expansion-panel >
<mat-expansion-panel-header>
<mat-panel-title>
Kraken
</mat-panel-title>
<mat-panel-description>
</mat-panel-description>
</mat-expansion-panel-header>
image and blurb
<form class="form-content">
<mat-list>
<mat-list-item >
<mat-icon matListIcon>attach_money</mat-icon>
<h4 matLine>BTC_USD</h4>
<button mat-button>SELECT</button>
</mat-list-item>
<mat-list-item >
<mat-icon matListIcon>attach_money</mat-icon>
<h4 matLine>LTC_USD</h4>
<button mat-button>SELECT</button>
</mat-list-item>
<mat-list-item >
<mat-icon matListIcon>attach_money</mat-icon>
<h4 matLine>ETH_USD</h4>
<button mat-button>SELECT</button>
</mat-list-item>
</mat-list>
</form>
</mat-expansion-panel>
</mat-accordion>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ExchangeGridComponent } from './exchange-grid.component';
describe('ExchangeGridComponent', () => {
let component: ExchangeGridComponent;
let fixture: ComponentFixture<ExchangeGridComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ExchangeGridComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ExchangeGridComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-exchange-grid',
templateUrl: './exchange-grid.component.html',
styleUrls: ['./exchange-grid.component.scss']
})
export class ExchangeGridComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@@ -0,0 +1,3 @@
<p>
history works!
</p>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HistoryComponent } from './history.component';
describe('HistoryComponent', () => {
let component: HistoryComponent;
let fixture: ComponentFixture<HistoryComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HistoryComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HistoryComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-history',
templateUrl: './history.component.html',
styleUrls: ['./history.component.scss']
})
export class HistoryComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@@ -1,2 +1,5 @@
<div class="container">
</div>
<h1 class="mat-h1">Welcome to GoCryptoTrader</h1>
<p>GoCryptoTrader is a multi-currency, multi-exchange trader for cryptocurrencies</p>
<p>It is under active development and you can see its development progress by clicking the trello button to the left</p>
<p>If you like what you see, consider clicking the donation button to the left</p>

View File

@@ -0,0 +1,3 @@
.example-card {
width: 400px;
}

View File

@@ -1,93 +1,79 @@
<div class="loading-spinner" *ngIf="settings === null">
<md-progress-spinner mode="indeterminate"></md-progress-spinner>
<mat-progress-spinner color="accent" mode="indeterminate"></mat-progress-spinner>
</div>
<div *ngIf="settings !== null">
<button (click)="saveSettings()" md-fab color="accent" class="md-fab md-fab-bottom-right">Save</button>
<form *ngIf="settings.SMSGlobal != null">
<md-card class="exchange-card">
<md-card-header>
<md-card-title>SMS Global Settings</md-card-title>
</md-card-header>
<md-card-content>
<table cellspacing="0">
<tr>
<td>
<md-checkbox name="smsEnabled" [(ngModel)]="settings.SMSGlobal.Enabled">Enabled</md-checkbox>
</td>
</tr>
</table>
<md-grid-list cols="2" rowHeight="3:1">
<md-grid-tile>
<md-form-field>
<input mdInput name="smsUsername" [(ngModel)]="settings.SMSGlobal.Username" [disabled]="!settings?.SMSGlobal.Enabled" placeholder="Username">
</md-form-field>
</md-grid-tile>
<md-grid-tile>
<md-form-field>
<input mdInput name="smsPassword" [(ngModel)]="settings.SMSGlobal.Password" [disabled]="!settings?.SMSGlobal.Enabled" placeholder="Password">
</md-form-field>
</md-grid-tile>
</md-grid-list>
<md-grid-list cols="3" rowHeight="2:1" *ngFor="let contact of settings.SMSGlobal?.Contacts">
<md-grid-tile>
<md-checkbox name="contactEnabled" [disabled]="!settings?.SMSGlobal.Enabled" [(ngModel)]="contact.Enabled">Enabled</md-checkbox>
</md-grid-tile>
<md-grid-tile>
<md-form-field>
<input mdInput name="contactUsername" [(ngModel)]="contact.Name" [disabled]="!settings.SMSGlobal.Enabled || !contact.Enabled" placeholder="Contact Name">
</md-form-field>
</md-grid-tile>
<md-grid-tile>
<md-form-field>
<input mdInput name="contactPassword" [(ngModel)]="contact.Number" [disabled]="!settings.SMSGlobal.Enabled || !contact.Enabled" placeholder="Contact Number">
</md-form-field>
</md-grid-tile>
</md-grid-list>
</md-card-content>
</md-card>
</form>
<form *ngFor="let exchange of settings?.Exchanges">
<md-card class="exchange-card">
<md-card-header>
<md-card-title>{{exchange.Name}} Exchange Settings</md-card-title>
</md-card-header>
<md-card-content>
<table cellspacing="0">
<tr>
<td>
<md-checkbox name="exchangeEnabled" [(ngModel)]="exchange.Enabled">Enabled</md-checkbox>
</td>
</tr>
</table>
<md-grid-list cols="3" rowHeight="2:1">
<md-grid-tile>
<md-form-field>
<input mdInput name="apiKey" [(ngModel)]="exchange.APIKey" [disabled]="!exchange.Enabled" placeholder="Exchange API Key">
</md-form-field>
</md-grid-tile>
<md-grid-tile>
<md-form-field>
<input mdInput name="apiSecretKey" [(ngModel)]="exchange.APISecret" [disabled]="!exchange.Enabled" placeholder="Exchange API Secret Key">
</md-form-field>
</md-grid-tile>
<md-grid-tile>
<md-form-field>
<input mdInput name="apiClientId" [(ngModel)]="exchange.ClientID" [disabled]="!exchange.Enabled" placeholder="Exchange API ClientID (optional)">
</md-form-field>
</md-grid-tile>
</md-grid-list>
<button matTooltip="Save" (click)="saveSettings()" mat-fab color="accent" class="mat-fab mat-fab-bottom-right"><mat-icon >save</mat-icon></button>
<label>Enabled Currencies</label>
<mat-accordion>
<mat-expansion-panel *ngIf="settings.SMSGlobal != null">
<mat-expansion-panel-header >
<mat-panel-title>
SMS Global
</mat-panel-title>
<mat-panel-description>
SMS configuration and contact management
<mat-icon>phone_iphone</mat-icon>
</mat-panel-description>
</mat-expansion-panel-header>
<form class="form-content">
<mat-checkbox name="smsEnabled" [(ngModel)]="settings.SMSGlobal.Enabled">Enabled</mat-checkbox>
<div mat-line></div>
<mat-form-field>
<input matInput name="smsUsername" [(ngModel)]="settings.SMSGlobal.Username" [disabled]="!settings?.SMSGlobal.Enabled" placeholder="Username">
</mat-form-field>
<mat-form-field>
<input matInput name="smsPassword" [(ngModel)]="settings.SMSGlobal.Password" [disabled]="!settings?.SMSGlobal.Enabled" placeholder="Password">
</mat-form-field>
<md-grid-list cols="6" rowHeight="2:1">
<md-grid-tile *ngFor="let currency of exchange.AvailablePairs.split(',')">
<md-checkbox name="availableCurrency" [disabled]="true || !exchange.Enabled">{{currency}}</md-checkbox>
</md-grid-tile>
</md-grid-list>
</md-card-content>
</md-card>
</form>
<div *ngFor="let contact of settings.SMSGlobal?.Contacts">
<mat-checkbox name="contactEnabled" [disabled]="!settings?.SMSGlobal.Enabled" [(ngModel)]="contact.Enabled">Enabled</mat-checkbox>
<div mat-line></div>
<mat-form-field>
<input matInput name="contactUsername" [(ngModel)]="contact.Name" [disabled]="!settings.SMSGlobal.Enabled || !contact.Enabled" placeholder="Contact Name" />
</mat-form-field>
<mat-form-field>
<input matInput name="contactPassword" [(ngModel)]="contact.Number" [disabled]="!settings.SMSGlobal.Enabled || !contact.Enabled" placeholder="Contact Number" />
</mat-form-field>
</div>
</form>
</mat-expansion-panel>
<mat-expansion-panel *ngFor="let exchange of settings?.Exchanges">
<mat-expansion-panel-header>
<mat-panel-title>
{{exchange.Name}}
</mat-panel-title>
<mat-panel-description>
Exchange Settings
<mat-icon>attach_money</mat-icon>
</mat-panel-description>
</mat-expansion-panel-header>
<form class="form-content">
<mat-checkbox name="exchangeEnabled" [(ngModel)]="exchange.Enabled">Enabled</mat-checkbox>
<div mat-line></div>
<mat-form-field>
<input matInput name="apiKey" [(ngModel)]="exchange.APIKey" [disabled]="!exchange.Enabled" placeholder="API Key*">
</mat-form-field>
<mat-form-field>
<input matInput name="apiSecretKey" [(ngModel)]="exchange.APISecret" [disabled]="!exchange.Enabled" placeholder="API Secret Key*">
</mat-form-field>
<mat-form-field>
<input matInput name="apiClientId" [(ngModel)]="exchange.ClientID" [disabled]="!exchange.Enabled" placeholder="API ClientID">
</mat-form-field>
<div mat-line></div>
<mat-panel-description>
Enabled Currency pairs {{enabledCurrencies.selectedOptions.selected.length}} - Managed via config temporarily
</mat-panel-description>
<div mat-line></div>
<mat-selection-list #enabledCurrencies>
<mat-list-option [disabled]="!exchange.Enabled || true" *ngFor="let currency of exchange.AvailablePairs.split(',')">
{{currency}}
</mat-list-option>
</mat-selection-list>
</form>
</mat-expansion-panel>
</mat-accordion>
</div>

View File

@@ -1,20 +1,12 @@
.example-form {
min-width: 150px;
width: 100%;
}
.example-full-width {
width: 100%;
}
.exchange-card {
margin-bottom: 20px;
width: 1000px;
}
.md-fab {
margin: 0;
// FAB
.mat-fab {
top: auto;
right: 30px;
bottom: 20px;
left: auto;
position: fixed;
bottom: 5%;
right: 2%;
}
.form-content {
margin: 20px;
}

View File

@@ -0,0 +1,2 @@
<app-buy-sell></app-buy-sell>
<button matTooltip="Trade" mat-fab color="accent" class="mat-fab mat-fab-bottom-right"><mat-icon >swap_horiz</mat-icon></button>

View File

@@ -0,0 +1,9 @@
.mat-fab {
top: auto;
right: 30px;
bottom: 20px;
left: auto;
position: fixed;
z-index: 3;
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TradingComponent } from './trading.component';
describe('TradingComponent', () => {
let component: TradingComponent;
let fixture: ComponentFixture<TradingComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TradingComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TradingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-trading',
templateUrl: './trading.component.html',
styleUrls: ['./trading.component.scss']
})
export class TradingComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@@ -0,0 +1,114 @@
<div class="loading-spinner" *ngIf="wallet === null">
<mat-progress-spinner color="accent" mode="indeterminate"></mat-progress-spinner>
</div>
<mat-accordion *ngIf="wallet !== null">
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
Coin Totals
</mat-panel-title>
</mat-expansion-panel-header>
<mat-list>
<mat-list-item *ngFor="let coin of wallet?.coin_totals">
<mat-icon mat-list-icon [ngClass]="coin.coin">{{coin.icon}}</mat-icon>
<h4 mat-line>{{coin.coin}}</h4>
<h4 mat-line>{{coin.balance}}</h4>
</mat-list-item>
</mat-list>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Online Summary
</mat-panel-title>
</mat-expansion-panel-header>
<mat-list>
<mat-list-item *ngFor="let coin of wallet?.online_summary.BTC">
<mat-icon mat-list-icon class="BTC">{{coinIcon('BTC')}}</mat-icon>
<h4 mat-line>Address: {{coin.address}}</h4>
<h4 mat-line>{{coin.balance}}{{coin.coin}} - {{coin.percentage}}%</h4>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item *ngFor="let coin of wallet?.online_summary.LTC">
<mat-icon mat-list-icon class="LTC">{{coinIcon('LTC')}}</mat-icon>
<h4 mat-line>Address: {{coin.address}}</h4>
<h4 mat-line>{{coin.balance}}{{coin.coin}} - {{coin.percentage}}%</h4>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item *ngFor="let coin of wallet?.online_summary.ETH">
<mat-icon mat-list-icon class="ETH">{{coinIcon('ETH')}}</mat-icon>
<h4 mat-line>Address: {{coin.address}}</h4>
<h4 mat-line>{{coin.balance}}{{coin.coin}} - {{coin.percentage}}%</h4>
</mat-list-item>
</mat-list>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Offline Summary
</mat-panel-title>
</mat-expansion-panel-header>
<mat-list>
<mat-list-item *ngFor="let coin of wallet?.offline_summary.BTC">
<mat-icon mat-list-icon class="BTC">{{coinIcon('BTC')}}</mat-icon>
<h4 mat-line>Address: {{coin.address}}</h4>
<h4 mat-line>{{coin.balance}}{{coin.coin}} - {{coin.percentage}}%</h4>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item *ngFor="let coin of wallet?.offline_summary.LTC">
<mat-icon mat-list-icon class="LTC">{{coinIcon('LTC')}}</mat-icon>
<h4 mat-line>Address: {{coin.address}}</h4>
<h4 mat-line>{{coin.balance}}{{coin.coin}} - {{coin.percentage}}%</h4>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item *ngFor="let coin of wallet?.offline_summary.ETH">
<mat-icon mat-list-icon class="ETH">{{coinIcon('ETH')}}</mat-icon>
<h4 mat-line>Address: {{coin.address}}</h4>
<h4 mat-line>{{coin.balance}}{{coin.coin}} - {{coin.percentage}}%</h4>
</mat-list-item>
</mat-list>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Online Coins
</mat-panel-title>
</mat-expansion-panel-header>
<mat-list>
<mat-list-item *ngFor="let coin of wallet?.coins_online">
<mat-icon mat-list-icon [ngClass]="coin.coin">{{coin.icon}}</mat-icon>
<h4 mat-line>{{coin.coin}}</h4>
<h4 mat-line>{{coin.balance}}</h4>
</mat-list-item>
</mat-list>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Offline Coins
</mat-panel-title>
</mat-expansion-panel-header>
<mat-list>
<mat-list-item *ngFor="let coin of wallet?.coins_offline">
<mat-icon mat-list-icon [ngClass]="coin.coin">{{coin.icon}}</mat-icon>
<h4 mat-line>{{coin.coin}}</h4>
<h4 mat-line>{{coin.balance}}</h4>
</mat-list-item>
</mat-list>
</mat-expansion-panel>
</mat-accordion>

View File

@@ -0,0 +1,14 @@
.wallet-card {
width: 80%;
margin: 10px auto;
}
.BTC {
color:orange;
}
.LTC {
color:silver;
}
.ETH {
color:darkslategrey;
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { WalletComponent } from './wallet.component';
describe('WalletComponent', () => {
let component: WalletComponent;
let fixture: ComponentFixture<WalletComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ WalletComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(WalletComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,87 @@
import { Component, OnInit } from '@angular/core';
import { WebsocketHandlerService } from './../../services/websocket-handler/websocket-handler.service';
import { Wallet, CoinTotal } from './../../shared/classes/wallet';
import {Sort} from '@angular/material';
@Component({
selector: 'app-wallet',
templateUrl: './wallet.component.html',
styleUrls: ['./wallet.component.scss']
})
export class WalletComponent implements OnInit {
private ws: WebsocketHandlerService;
private failCount = 0;
private timer: any;
public wallet: Wallet;
displayedColumns = ['coin', 'balance'];
private getWalletMessage = {
Event: 'GetPortfolio',
data: null,
};
constructor(private websocketHandler: WebsocketHandlerService) {
this.wallet= null;
this.ws = websocketHandler;
this.ws.messages.subscribe(msg => {
if (msg.Event === 'GetPortfolio') {
console.log(JSON.stringify(msg.data));
this.wallet = <Wallet>msg.data;
this.attachIcon(this.wallet.coin_totals);
this.attachIcon(this.wallet.coins_offline);
this.attachIcon(this.wallet.coins_online);
this.attachIcon(this.wallet.offline_summary.BTC);
this.attachIcon(this.wallet.offline_summary.ETH);
this.attachIcon(this.wallet.offline_summary.LTC);
this.attachIcon(this.wallet.online_summary.BTC);
this.attachIcon(this.wallet.online_summary.ETH);
this.attachIcon(this.wallet.online_summary.LTC);
}
});
}
public coinIcon(coin:string) :string {
switch(coin) {
case "BTC": return "attach_money";
case "LTC": return "attach_money";
case "ETH": return "attach_money";
}
}
public attachIcon(items: CoinTotal[]): void {
if (items) {
for (var i = 0; i < items.length; i++) {
items[i].icon = this.coinIcon(items[i].coin);
}
}
}
ngOnInit() {
this.setWallet();
}
//there has to be a better way
private resendMessageIfPageRefreshed(): void {
if (this.failCount <= 10) {
setTimeout(() => {
if (this.wallet === null || this.wallet === undefined) {
this.failCount++;
this.setWallet();
}
}, 1000);
} else {
console.log('Could not load wallet. Check if GocryptoTrader server is running, otherwise open a ticket');
}
}
private setWallet():void {
this.ws.messages.next(this.getWalletMessage);
this.resendMessageIfPageRefreshed();
}
}

View File

@@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { SidebarService } from './sidebar.service';
describe('SidebarService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [SidebarService]
});
});
it('should be created', inject([SidebarService], (service: SidebarService) => {
expect(service).toBeTruthy();
}));
});

View File

@@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';
import { MatSidenav, MatDrawerToggleResult } from '@angular/material';
@Injectable()
export class SidebarService {
private sidenav: MatSidenav;
/**
* Setter for sidenav.
*
* @param {MatSidnav} sidenav
*/
public setSidenav(sidenav: MatSidenav) {
this.sidenav = sidenav;
}
/**
* Open this sidenav, and return a Promise that will resolve when it's fully opened (or get rejected if it didn't).
*
* @returns Promise<MatSidnavToggleResult>
*/
public open(): Promise<MatDrawerToggleResult> {
this.sidenav.open();
return;
}
/**
* Close this sidenav, and return a Promise that will resolve when it's fully closed (or get rejected if it didn't).
*
* @returns Promise<MatSidnavToggleResult>
*/
public close(): Promise<MatDrawerToggleResult> {
this.sidenav.close();
return;
}
/**
* Toggle this sidenav. This is equivalent to calling open() when it's already opened, or close() when it's closed.
*
* @param {boolean} isOpen Whether the sidenav should be open.
*
* @returns {Promise<MatSidnavToggleResult>}
*/
public toggle(isOpen?: boolean): Promise<MatDrawerToggleResult> {
this.sidenav.toggle(isOpen);
return;
}
}

View File

@@ -0,0 +1,54 @@
import {inject, TestBed} from '@angular/core/testing';
import {HttpModule} from '@angular/http';
import {StyleManagerComponent} from './style-manager.component';
describe('StyleManager', () => {
let styleManager: StyleManagerComponent;
beforeEach(() => TestBed.configureTestingModule({
imports: [HttpModule],
providers: [StyleManagerComponent]
}));
beforeEach(inject([StyleManagerComponent], (sm: StyleManagerComponent) => {
styleManager = sm;
}));
afterEach(() => {
let links = document.head.querySelectorAll('link');
for (let link of Array.prototype.slice.call(links)) {
if (link.className.includes('style-manager-')) {
document.head.removeChild(link);
}
}
});
it('should add stylesheet to head', () => {
styleManager.setStyle('test', 'test.css');
let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
expect(styleEl).not.toBeNull();
expect(styleEl.href.endsWith('test.css')).toBe(true);
});
it('should change existing stylesheet', () => {
styleManager.setStyle('test', 'test.css');
let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
expect(styleEl).not.toBeNull();
expect(styleEl.href.endsWith('test.css')).toBe(true);
styleManager.setStyle('test', 'new.css');
expect(styleEl.href.endsWith('new.css')).toBe(true);
});
it('should remove existing stylesheet', () => {
styleManager.setStyle('test', 'test.css');
let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
expect(styleEl).not.toBeNull();
expect(styleEl.href.endsWith('test.css')).toBe(true);
styleManager.removeStyle('test');
styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
expect(styleEl).toBeNull();
});
});

View File

@@ -0,0 +1,44 @@
import {Injectable} from '@angular/core';
/**
* Class for managing stylesheets. Stylesheets are loaded into named slots so that they can be
* removed or changed later.
*/
@Injectable()
export class StyleManagerService {
/**
* Set the stylesheet with the specified key.
*/
setStyle(key: string, href: string) {
getLinkElementForKey(key).setAttribute('href', href);
}
/**
* Remove the stylesheet with the specified key.
*/
removeStyle(key: string) {
const existingLinkElement = getExistingLinkElementByKey(key);
if (existingLinkElement) {
document.head.removeChild(existingLinkElement);
}
}
}
function getLinkElementForKey(key: string) {
return getExistingLinkElementByKey(key) || createLinkElementWithKey(key);
}
function getExistingLinkElementByKey(key: string) {
return document.head.querySelector(`link[rel="stylesheet"].${getClassNameForKey(key)}`);
}
function createLinkElementWithKey(key: string) {
const linkEl = document.createElement('link');
linkEl.setAttribute('rel', 'stylesheet');
linkEl.classList.add(getClassNameForKey(key));
document.head.appendChild(linkEl);
return linkEl;
}
function getClassNameForKey(key: string) {
return `style-manager-${key}`;
}

View File

@@ -0,0 +1,52 @@
import {ThemeStorageService} from './theme-storage.service';
const testStorageKey = ThemeStorageService.storageKey;
const testTheme = {
primary: '#000000',
accent: '#ffffff',
href: 'test/path/to/theme'
};
const createTestData = () => {
window.localStorage[testStorageKey] = JSON.stringify(testTheme);
};
const clearTestData = () => {
window.localStorage.clear();
};
describe('ThemeStorage Service', () => {
const service = new ThemeStorageService();
const getCurrTheme = () => JSON.parse(window.localStorage.getItem(testStorageKey));
const secondTestTheme = {
primary: '#666666',
accent: '#333333',
href: 'some/cool/path'
};
beforeEach(createTestData);
afterEach(clearTestData);
it('should set the current theme', () => {
expect(getCurrTheme()).toEqual(testTheme);
service.storeTheme(secondTestTheme);
expect(getCurrTheme()).toEqual(secondTestTheme);
});
it('should get the current theme', () => {
const theme = service.getStoredTheme();
expect(theme).toEqual(testTheme);
});
it('should clear the stored theme data', () => {
expect(getCurrTheme()).not.toBeNull();
service.clearStorage();
expect(getCurrTheme()).toBeNull();
});
it('should emit an event when setTheme is called', () => {
spyOn(service.onThemeUpdate, 'emit');
service.storeTheme(secondTestTheme);
expect(service.onThemeUpdate.emit).toHaveBeenCalled();
expect(service.onThemeUpdate.emit).toHaveBeenCalledWith(secondTestTheme);
});
});

View File

@@ -0,0 +1,39 @@
import {Injectable, EventEmitter} from '@angular/core';
export interface DocsSiteTheme {
href: string;
accent: string;
primary: string;
isDark?: boolean;
isDefault?: boolean;
}
@Injectable()
export class ThemeStorageService {
static storageKey = 'docs-theme-storage-current';
public onThemeUpdate: EventEmitter<DocsSiteTheme> = new EventEmitter<DocsSiteTheme>();
public storeTheme(theme: DocsSiteTheme) {
try {
window.localStorage[ThemeStorageService.storageKey] = JSON.stringify(theme);
} catch (e) { }
this.onThemeUpdate.emit(theme);
}
public getStoredTheme(): DocsSiteTheme {
try {
return JSON.parse(window.localStorage[ThemeStorageService.storageKey] || null);
} catch (e) {
return null;
}
}
public clearStorage() {
try {
window.localStorage.removeItem(ThemeStorageService.storageKey);
} catch (e) { }
}
}

View File

@@ -1,13 +1,13 @@
import { Injectable } from '@angular/core';
import * as Rx from 'rxjs/Rx';
import {Subject, Observable, Observer } from 'rxjs/Rx';
@Injectable()
export class WebsocketService {
constructor() { }
private subject: Rx.Subject<MessageEvent>;
private subject: Subject<MessageEvent>;
public connect(url): Rx.Subject<MessageEvent> {
public connect(url): Subject<MessageEvent> {
if (!this.subject) {
this.subject = this.create(url);
}
@@ -21,11 +21,11 @@ export class WebsocketService {
private isAuth = false;
private create(url): Rx.Subject<MessageEvent> {
private create(url): Subject<MessageEvent> {
let ws = new WebSocket(url);
let observable = Rx.Observable.create(
(obs: Rx.Observer<MessageEvent>) => {
let observable = Observable.create(
(obs: Observer<MessageEvent>) => {
ws.onmessage = obs.next.bind(obs);
ws.onerror = obs.error.bind(obs);
ws.onclose = obs.complete.bind(obs);
@@ -40,6 +40,6 @@ let observer = {
}
}
}
return Rx.Subject.create(observer, observable);
return Subject.create(observer, observable);
}
}

View File

@@ -1,8 +1,8 @@
<div class="loading-spinner" *ngIf="tickerCards === null || tickerCards.length === 0">
<md-progress-spinner mode="indeterminate"></md-progress-spinner>
<mat-progress-spinner color="accent" mode="indeterminate"></mat-progress-spinner>
</div>
<md-grid-list cols="4" rowHeight="4:3" >
<md-grid-tile *ngFor="let ticker of tickerCards">
<mat-grid-list cols="4" rowHeight="4:3" >
<mat-grid-tile *ngFor="let ticker of tickerCards">
<app-exchange-currency-ticker [ticker]="ticker" ></app-exchange-currency-ticker>
</md-grid-tile>
</md-grid-list>
</mat-grid-tile>
</mat-grid-list>

View File

@@ -0,0 +1,3 @@
<p>
buy-sell-form works!
</p>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BuySellFormComponent } from './buy-sell-form.component';
describe('BuySellFormComponent', () => {
let component: BuySellFormComponent;
let fixture: ComponentFixture<BuySellFormComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ BuySellFormComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BuySellFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-buy-sell-form',
templateUrl: './buy-sell-form.component.html',
styleUrls: ['./buy-sell-form.component.scss']
})
export class BuySellFormComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@@ -0,0 +1,14 @@
<mat-tab-group>
<mat-tab>
<ng-template mat-tab-label>
BUY
</ng-template>
<app-buy-sell-form></app-buy-sell-form>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
SELL
</ng-template>
<app-buy-sell-form></app-buy-sell-form>
</mat-tab>
</mat-tab-group>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BuySellComponent } from './buy-sell.component';
describe('BuySellComponent', () => {
let component: BuySellComponent;
let fixture: ComponentFixture<BuySellComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ BuySellComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BuySellComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit,Directive, ViewContainerRef } from '@angular/core';
@Component({
selector: 'app-buy-sell',
templateUrl: './buy-sell.component.html',
styleUrls: ['./buy-sell.component.scss']
})
export class BuySellComponent implements OnInit {
constructor(public viewContainerRef: ViewContainerRef) { }
ngOnInit() {
}
}

View File

View File

@@ -0,0 +1,22 @@
export interface CoinTotal {
coin: string;
balance: number;
percentage: number;
address: string;
icon:string;
}
export interface Summary {
BTC: CoinTotal[];
ETH: CoinTotal[];
LTC: CoinTotal[];
}
export interface Wallet {
coin_totals: CoinTotal[];
coins_offline: CoinTotal[];
offline_summary: Summary;
coins_online: CoinTotal[];
online_summary: Summary;
}

View File

@@ -1,28 +1,28 @@
<md-card class="exchange-card one-time-animation" >
<md-card-header>
<md-card-title>{{ticker?.Exchange}} {{ticker?.CurrencyPair}} Ticker update</md-card-title>
</md-card-header>
<md-card-content>
<md-grid-list cols="3" rowHeight="16:9" >
<md-grid-tile>
<mat-card class="exchange-card one-time-animation" >
<mat-card-header>
<mat-card-title>{{ticker?.Exchange}} {{ticker?.CurrencyPair}} Ticker update</mat-card-title>
</mat-card-header>
<mat-card-content>
<mat-grid-list cols="3" rowHeight="16:9" >
<mat-grid-tile>
<p>Last: {{ticker?.Last | number:'1.0-1'}}</p>
</md-grid-tile>
<md-grid-tile>
</mat-grid-tile>
<mat-grid-tile>
<p> Low: {{ticker?.Low | number:'1.0-1'}}</p>
</md-grid-tile>
<md-grid-tile>
</mat-grid-tile>
<mat-grid-tile>
<p> High: {{ticker?.High | number:'1.0-1'}}</p>
</md-grid-tile>
</mat-grid-tile>
<md-grid-tile>
<mat-grid-tile>
<p> Bid: {{ticker?.Bid | number:'1.0-1'}}</p>
</md-grid-tile>
<md-grid-tile>
</mat-grid-tile>
<mat-grid-tile>
<p> Ask: {{ticker?.Ask | number:'1.0-1'}}</p>
</md-grid-tile>
<md-grid-tile>
</mat-grid-tile>
<mat-grid-tile>
<p>Volume: {{ticker?.Volume | number:'1.0-1'}}</p>
</md-grid-tile>
</md-grid-list>
</md-card-content>
</mat-grid-tile>
</mat-grid-list>
</mat-card-content>

View File

@@ -0,0 +1,5 @@
<mat-card class="my-orders-card" flex="33">
<mat-card-header>
<mat-card-title>My Orders</mat-card-title>
</mat-card-header>
</mat-card>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MyOrdersComponent } from './my-orders.component';
describe('MyOrdersComponent', () => {
let component: MyOrdersComponent;
let fixture: ComponentFixture<MyOrdersComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MyOrdersComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyOrdersComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-my-orders',
templateUrl: './my-orders.component.html',
styleUrls: ['./my-orders.component.scss']
})
export class MyOrdersComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@@ -1,16 +1,14 @@
<!-- TODO: figure out if the <nav> should go inside of a <header> element. -->
<nav class="docs-navbar">
<md-toolbar color="primary">
<a md-button class="docs-button" routerLink="/" aria-label="Angular Material">
<span>Logo</span>
</a>
<a md-button class="docs-button" routerLink="dashboard">Dashboard</a>
<a md-button class="docs-button" routerLink="about">Wallet Summary</a>
<a md-button class="docs-button" routerLink="settings">Settings</a>
<a md-button class="docs-button" routerLink="about">Help</a>
<div class="flex-spacer"></div>
<a md-button class="docs-button" href="https://github.com/thrasher-/gocryptotrader/issues" aria-label="GitHub Repository">
Report issue
</a>
</md-toolbar>
<mat-toolbar color="primary">
<a (click)="sidebarService.toggle()" class="material-icons">&#xE5D2;</a>
<a mat-button class="docs-button" routerLink="/" aria-label="Angular Material">
<span>GoCryptoTrader</span>
</a>
<app-selected-currency></app-selected-currency>
<div class="flex-spacer"></div>
<theme-picker></theme-picker>
&nbsp;
&nbsp;
</mat-toolbar>
</nav>

View File

@@ -0,0 +1,7 @@
.material-icons {
cursor: pointer;
}
.flex-spacer {
flex-grow: 1;
}

View File

@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { SidebarService } from './../../services/sidebar/sidebar.service';
@Component({
selector: 'app-navbar',
@@ -6,8 +7,10 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent implements OnInit {
constructor() { }
sidebarService: SidebarService
constructor(something: SidebarService) {
this.sidebarService = something;
}
ngOnInit() {
}

View File

@@ -0,0 +1,5 @@
<mat-card class="orders-card" flex="33">
<mat-card-header>
<mat-card-title>Orders</mat-card-title>
</mat-card-header>
</mat-card>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { OrdersComponent } from './orders.component';
describe('OrdersComponent', () => {
let component: OrdersComponent;
let fixture: ComponentFixture<OrdersComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ OrdersComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(OrdersComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-orders',
templateUrl: './orders.component.html',
styleUrls: ['./orders.component.scss']
})
export class OrdersComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@@ -0,0 +1,5 @@
<mat-card class="price-history-card" flex="33">
<mat-card-header>
<mat-card-title>Price History</mat-card-title>
</mat-card-header>
</mat-card>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PriceHistoryComponent } from './price-history.component';
describe('PriceHistoryComponent', () => {
let component: PriceHistoryComponent;
let fixture: ComponentFixture<PriceHistoryComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PriceHistoryComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PriceHistoryComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-price-history',
templateUrl: './price-history.component.html',
styleUrls: ['./price-history.component.scss']
})
export class PriceHistoryComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@@ -0,0 +1,11 @@
<button mat-button routerLink="exchange-grid" matTooltip="Change currency">POLONIEX: BTC_USD (placeholder)</button>
<button mat-icon-button routerLink="currency-list" matTooltip="View currency list">
<mat-icon>
view_list
</mat-icon>
</button>
<button mat-icon-button routerLink="exchange-grid" matTooltip="View exchange grid">
<mat-icon>
view_stream
</mat-icon>
</button>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SelectedCurrencyComponent } from './selected-currency.component';
describe('SelectedCurrencyComponent', () => {
let component: SelectedCurrencyComponent;
let fixture: ComponentFixture<SelectedCurrencyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SelectedCurrencyComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SelectedCurrencyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-selected-currency',
templateUrl: './selected-currency.component.html',
styleUrls: ['./selected-currency.component.scss']
})
export class SelectedCurrencyComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@@ -0,0 +1,18 @@
<button mat-icon-button [mat-menu-trigger-for]="themeMenu" matTooltip="Select a theme!"
tabindex="-1">
<mat-icon>format_color_fill</mat-icon>
</button>
<!-- TODO: replace use of `mat-menu` here with a custom overlay -->
<mat-menu class="docs-theme-picker-menu" #themeMenu="matMenu" x-position="before">
<mat-grid-list cols="2">
<mat-grid-tile *ngFor="let theme of themes">
<div mat-menu-item (click)="installTheme(theme)">
<div class="docs-theme-picker-swatch">
<mat-icon class="docs-theme-chosen-icon" *ngIf="currentTheme === theme">check_circle</mat-icon>
<div class="docs-theme-picker-primary" [style.background]="theme.primary"></div>
</div>
</div>
</mat-grid-tile>
</mat-grid-list>
</mat-menu>

View File

@@ -0,0 +1,58 @@
$theme-picker-menu-padding: 8px;
$theme-picker-grid-cell-size: 48px;
$theme-picker-grid-cells-per-row: 2;
$theme-picker-swatch-size: 36px;
$theme-picker-accent-stripe-size: 6px;
.docs-theme-picker-menu {
.mat-menu-content {
padding: $theme-picker-menu-padding;
}
[mat-menu-item] {
flex: 0 0 auto;
padding: 0;
overflow: hidden;
}
.docs-theme-picker-swatch {
position: relative;
width: $theme-picker-swatch-size;
height: $theme-picker-swatch-size;
margin: ($theme-picker-grid-cell-size - $theme-picker-swatch-size) / 2;
border-radius: 50%;
overflow: hidden;
.docs-theme-chosen-icon {
color: white;
position: absolute;
left: 50%; top: 50%;
transform: translate(-50%, -50%);
}
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
border: 1px solid rgba(0,0,0,.2);
border-radius: 50%;
}
}
.docs-theme-picker-primary {
width: 100%;
height: 100%;
}
.docs-theme-picker-accent {
position: absolute;
bottom: $theme-picker-accent-stripe-size;
width: 100%;
height: $theme-picker-accent-stripe-size;
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ThemePickerComponent } from './theme-picker.component';
describe('ThemePickerComponent', () => {
let component: ThemePickerComponent;
let fixture: ComponentFixture<ThemePickerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ThemePickerComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ThemePickerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,74 @@
import {Component, ViewEncapsulation, ChangeDetectionStrategy, NgModule} from '@angular/core';
import { StyleManagerService } from './../../services/style-manager/style-manager.service';
import { ThemeStorageService,DocsSiteTheme } from './../../services/theme-storage/theme-storage.service';
import {CommonModule} from '@angular/common';
@Component({
selector: 'theme-picker',
templateUrl: 'theme-picker.html',
styleUrls: ['theme-picker.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: {'aria-hidden': 'true'},
})
export class ThemePickerComponent {
currentTheme;
themes = [
{
primary: '#673AB7',
accent: '#FFC107',
href: 'deeppurple-amber.css',
isDark: false,
},
{
primary: '#3F51B5',
accent: '#E91E63',
href: 'indigo-pink.css',
isDark: false,
isDefault: true,
},
{
primary: '#E91E63',
accent: '#607D8B',
href: 'pink-bluegrey.css',
isDark: true,
},
{
primary: '#9C27B0',
accent: '#4CAF50',
href: 'purple-green.css',
isDark: true,
},
];
constructor(
public styleManager: StyleManagerService,
private _themeStorage: ThemeStorageService
) {
const currentTheme = this._themeStorage.getStoredTheme();
if (currentTheme) {
this.installTheme(currentTheme);
}
}
installTheme(theme: DocsSiteTheme) {
this.currentTheme = this._getCurrentThemeFromHref(theme.href);
if (theme.isDefault) {
this.styleManager.removeStyle('theme');
} else {
this.styleManager.setStyle('theme', `assets/${theme.href}`);
}
if (this.currentTheme) {
this._themeStorage.storeTheme(this.currentTheme);
}
}
private _getCurrentThemeFromHref(href: string): DocsSiteTheme {
return this.themes.find(theme => theme.href === href);
}
}

View File

@@ -0,0 +1,5 @@
<mat-card class="trade-history-card" flex="33">
<mat-card-header>
<mat-card-title>Trade History</mat-card-title>
</mat-card-header>
</mat-card>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TradeHistoryComponent } from './trade-history.component';
describe('TradeHistoryComponent', () => {
let component: TradeHistoryComponent;
let fixture: ComponentFixture<TradeHistoryComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TradeHistoryComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TradeHistoryComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-trade-history',
templateUrl: './trade-history.component.html',
styleUrls: ['./trade-history.component.scss']
})
export class TradeHistoryComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -14,7 +14,7 @@
</head>
<body class="mat-app-background">
<app-root>Loading...</app-root>
<app-root><img style="position: absolute;top: 25%;left: 40%; width: 25%;" src="/assets/page-logo.png" /></app-root>
</body>
</html>

View File

@@ -4,26 +4,47 @@ html,
body {
margin: 0;
padding: 0;
height: 100%;
}
.main {
padding: 20px 70px 0;
display: block;
text-align: center;
}
.main-content {
display: inline-block;
text-align: left;
max-width: 940px;
width:100%;
font-family: Roboto,Helvetica Neue Light,Helvetica Neue,Helvetica,Arial,Lucida Grande,sans-serif;
}
.loading-spinner {
margin-left:50%;
margin-right:50%;
width:100%;
margin-left: 50%;
margin-right: 50%;
}
////////////////////////////////////////////////////////////////
// Default settings for cards
////////////////////////////////////////////////////////////////
.card {
width: 80%;
margin: 10px auto;
}
////////////////////////////////////////////////////////////////
// Default settings for expandable tile menus
////////////////////////////////////////////////////////////////
.mat-expansion-panel {
width: 80%;
margin: 0px auto !important;
}
.mat-expansion-panel-header-title,
.mat-expansion-panel-header-description {
flex-basis: 0;
}
.mat-expansion-panel-header-description {
justify-content: space-between;
align-items: center;
}
.mat-expansion-panel-spacing {
margin: 16px auto !important;
width: 85%;
}
.mat-drawer {
background: none !important;
}
@import '~@angular/material/prebuilt-themes/indigo-pink.css';

View File

@@ -1,6 +1,7 @@
{
"compileOnSave": false,
"compilerOptions": {
"module":"system",
"outDir": "./dist/out-tsc",
"baseUrl": "src",
"sourceMap": true,
@@ -8,7 +9,7 @@
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowJs": true,
"allowJs": false,
"target": "es5",
"paths": {
"environments": [

View File

@@ -9,7 +9,7 @@ const postcssUrl = require('postcss-url');
const { NoEmitOnErrorsPlugin, LoaderOptionsPlugin, DefinePlugin, HashedModuleIdsPlugin } = require('webpack');
const { GlobCopyWebpackPlugin, BaseHrefWebpackPlugin } = require('@angular/cli/plugins/webpack');
const { CommonsChunkPlugin, UglifyJsPlugin } = require('webpack').optimize;
const { AotPlugin } = require('@ngtools/webpack');
const { AngularCompilerPlugin } = require('@ngtools/webpack');
const nodeModules = path.join(process.cwd(), 'node_modules');
const entryPoints = ["inline", "polyfills", "sw-register", "styles", "vendor", "main"];
@@ -139,7 +139,7 @@ function getPlugins() {
"hashDigestLength": 4
}));
plugins.push(new AotPlugin({
plugins.push(new AngularCompilerPlugin({
"mainPath": "main.ts",
"hostReplacementPaths": {
"environments/index.ts": "environments/index.prod.ts"
@@ -160,7 +160,7 @@ function getPlugins() {
}));
} else {
plugins.push(new AotPlugin({
plugins.push(new AngularCompilerPlugin({
"mainPath": "main.ts",
"hostReplacementPaths": {
"environments/index.ts": "environments/index.ts"
@@ -364,7 +364,7 @@ module.exports = {
})
},
{
"test": /\.ts$/,
"test": /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
"loader": "@ngtools/webpack"
}
]