Bisq markets dashboard. Base views. WIP.

This commit is contained in:
softsimon 2021-02-27 04:19:56 +07:00
parent be0fd7c582
commit eeb7447988
No known key found for this signature in database
GPG key ID: 488D7DCFB5A430D7
15 changed files with 325 additions and 5 deletions

View file

@ -31,6 +31,7 @@
"clipboard": "^2.0.4",
"domino": "^2.1.6",
"express": "^4.15.2",
"lightweight-charts": "^3.3.0",
"ngx-bootrap-multiselect": "^2.0.0",
"ngx-infinite-scroll": "^9.0.0",
"qrcode": "^1.4.4",
@ -6857,6 +6858,11 @@
"node >=0.6.0"
]
},
"node_modules/fancy-canvas": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/fancy-canvas/-/fancy-canvas-0.2.2.tgz",
"integrity": "sha512-50qi8xA0QkHbjmb8h7XQ6k2fvD7y/yMfiUw9YTarJ7rWrq6o5/3CCXPouYk+XSLASvvxtjyiQLRBFt3qkE3oyA=="
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -9668,6 +9674,14 @@
"immediate": "~3.0.5"
}
},
"node_modules/lightweight-charts": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lightweight-charts/-/lightweight-charts-3.3.0.tgz",
"integrity": "sha512-W5jeBrXcHG8eHnIQ0L2CB9TLkrrsjNPlQq5SICPO8PnJ3dJ8jZkLCAwemZ7Ym7ZGCfKCz6ow1EPbyzNYxblnkw==",
"dependencies": {
"fancy-canvas": "0.2.2"
}
},
"node_modules/limiter": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
@ -24769,6 +24783,11 @@
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
"dev": true
},
"fancy-canvas": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/fancy-canvas/-/fancy-canvas-0.2.2.tgz",
"integrity": "sha512-50qi8xA0QkHbjmb8h7XQ6k2fvD7y/yMfiUw9YTarJ7rWrq6o5/3CCXPouYk+XSLASvvxtjyiQLRBFt3qkE3oyA=="
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -27006,6 +27025,14 @@
"immediate": "~3.0.5"
}
},
"lightweight-charts": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lightweight-charts/-/lightweight-charts-3.3.0.tgz",
"integrity": "sha512-W5jeBrXcHG8eHnIQ0L2CB9TLkrrsjNPlQq5SICPO8PnJ3dJ8jZkLCAwemZ7Ym7ZGCfKCz6ow1EPbyzNYxblnkw==",
"requires": {
"fancy-canvas": "0.2.2"
}
},
"limiter": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",

View file

@ -61,6 +61,7 @@
"clipboard": "^2.0.4",
"domino": "^2.1.6",
"express": "^4.15.2",
"lightweight-charts": "^3.3.0",
"ngx-bootrap-multiselect": "^2.0.0",
"ngx-infinite-scroll": "^9.0.0",
"qrcode": "^1.4.4",

View file

@ -42,4 +42,21 @@ export class BisqApiService {
getAddress$(address: string): Observable<BisqTransaction[]> {
return this.httpClient.get<BisqTransaction[]>(API_BASE_URL + '/address/' + address);
}
getMarkets$(): Observable<any[]> {
return this.httpClient.get<any[]>(API_BASE_URL + '/markets/markets');
}
getMarketsTicker$(): Observable<any[]> {
return this.httpClient.get<any[]>(API_BASE_URL + '/markets/ticker');
}
getMarketsCurrencies$(): Observable<any[]> {
return this.httpClient.get<any[]>(API_BASE_URL + '/markets/currencies');
}
getMarketsHloc$(market: string, interval: 'minute' | 'half_hour' | 'hour' | 'half_day' | 'day'
| 'week' | 'month' | 'year' | 'auto'): Observable<any[]> {
return this.httpClient.get<any[]>(API_BASE_URL + '/markets/hloc?market=' + market + '&interval=' + interval);
}
}

View file

@ -0,0 +1,34 @@
<div class="container-xl">
<h1>Active Markets</h1>
<ng-container *ngIf="{ value: (tickers$ | async) } as tickers">
<table class="table table-borderless table-striped">
<thead>
<th style="width: 20%;" i18n>Coin</th>
<th class="d-none d-md-block" style="width: 100%;" i18n>Pair</th>
<th style="width: 20%;" i18n>Price</th>
<th style="width: 20%;" i18n>24h volume</th>
<th class="d-none d-md-block" i18n>Volume %</th>
</thead>
<tbody *ngIf="tickers.value; else loadingTmpl">
<tr *ngFor="let ticker of tickers.value; trackBy: trackByFn">
<td>{{ ticker.market.lname }}</td>
<td><a [routerLink]="['/market' | relativeUrl, ticker.pair_url]">{{ ticker.pair }}</a></td>
<td>
<app-fiat *ngIf="ticker.market.rtype === 'crypto'; else fiat" [value]="ticker.last * 100000000"></app-fiat>
<ng-template #fiat><span class="green-color">{{ ticker.last | currency: ticker.market.rsymbol }}</span></ng-template>
</td>
</tr>
</tbody>
</table>
<br>
</ng-container>
</div>
<ng-template #loadingTmpl>
<tr *ngFor="let i of [1,2,3,4,5,6,7,8,9,10]">
<td *ngFor="let j of [1, 2, 3, 4, 5]"><span class="skeleton-loader"></span></td>
</tr>
</ng-template>

View file

@ -0,0 +1,43 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Observable, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
import { BisqApiService } from '../bisq-api.service';
@Component({
selector: 'app-bisq-dashboard',
templateUrl: './bisq-dashboard.component.html',
styleUrls: ['./bisq-dashboard.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BisqDashboardComponent implements OnInit {
tickers$: Observable<any>;
constructor(
private bisqApiService: BisqApiService,
) { }
ngOnInit(): void {
this.tickers$ = combineLatest([
this.bisqApiService.getMarketsTicker$(),
this.bisqApiService.getMarkets$()
])
.pipe(
map(([tickers, markets]) => {
const newTickers = [];
for (const t in tickers) {
tickers[t].pair_url = t;
tickers[t].pair = t.replace('_', '/').toUpperCase();
tickers[t].market = markets[t];
newTickers.push(tickers[t]);
}
console.log(newTickers);
return newTickers;
})
);
}
trackByFn(index: number) {
return index;
}
}

View file

@ -0,0 +1,49 @@
<div class="container-xl">
<ng-container *ngIf="hlocData$ | async as hlocData">
<ng-container *ngIf="currency$ | async as currency">
<h1>{{ currency.market.lname }} - {{ currency.pair }}</h1>
<div class="float-left">
<b>{{ hlocData[hlocData.length - 1].close | currency: currency.market.rsymbol }}</b>
</div>
</ng-container>
<form [formGroup]="radioGroupForm" class="mb-3 float-right">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="interval">
<!--
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'minute'"> 1M
</label>
-->
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'half_hour'"> 30M
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'hour'"> 1H
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'half_day'"> 12H
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'day'"> 1D
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'week'"> 1W
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'month'"> 1M
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'year'"> 1Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'auto'" fragment="auto"> Auto
</label>
</div>
</form>
<app-lightweight-charts [data]="hlocData"></app-lightweight-charts>
</ng-container>
</div>

View file

@ -0,0 +1,64 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { combineLatest, merge, Observable, of } from 'rxjs';
import { filter, map, mergeAll, switchMap, tap } from 'rxjs/operators';
import { BisqApiService } from '../bisq-api.service';
@Component({
selector: 'app-bisq-market',
templateUrl: './bisq-market.component.html',
styleUrls: ['./bisq-market.component.scss']
})
export class BisqMarketComponent implements OnInit {
hlocData$: Observable<any>;
currency$: Observable<any>;
radioGroupForm: FormGroup;
defaultInterval = 'half_hour';
constructor(
private route: ActivatedRoute,
private bisqApiService: BisqApiService,
private formBuilder: FormBuilder,
) { }
ngOnInit(): void {
this.radioGroupForm = this.formBuilder.group({
interval: [this.defaultInterval],
});
this.currency$ = this.bisqApiService.getMarkets$()
.pipe(
switchMap((markets) => combineLatest([of(markets), this.route.paramMap])),
map(([markets, routeParams]) => {
const pair = routeParams.get('pair');
console.log(markets);
return {
pair: pair.replace('_', '/').toUpperCase(),
market: markets[pair],
};
})
);
this.hlocData$ = combineLatest([
this.route.paramMap,
merge(this.radioGroupForm.get('interval').valueChanges, of(this.defaultInterval)),
])
.pipe(
switchMap(([routeParams, interval]) => {
const pair = routeParams.get('pair');
return this.bisqApiService.getMarketsHloc$(pair, interval);
}),
map((hloc) => {
return hloc.map((h) => {
h.time = h.period_start;
return h;
});
}),
tap((data) => {
console.log(data);
}),
);
}
}

View file

@ -3,10 +3,13 @@ import { BisqRoutingModule } from './bisq.routing.module';
import { SharedModule } from '../shared/shared.module';
import { NgxBootstrapMultiselectModule } from 'ngx-bootrap-multiselect';
import { LightweightChartsComponent } from './lightweight-charts/lightweight-charts.component';
import { BisqMarketComponent } from './bisq-market/bisq-market.component';
import { BisqTransactionsComponent } from './bisq-transactions/bisq-transactions.component';
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
import { BisqTransactionComponent } from './bisq-transaction/bisq-transaction.component';
import { BisqBlockComponent } from './bisq-block/bisq-block.component';
import { BisqDashboardComponent } from './bisq-dashboard/bisq-dashboard.component';
import { BisqIconComponent } from './bisq-icon/bisq-icon.component';
import { BisqTransactionDetailsComponent } from './bisq-transaction-details/bisq-transaction-details.component';
import { BisqTransfersComponent } from './bisq-transfers/bisq-transfers.component';
@ -34,6 +37,9 @@ import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
BisqAddressComponent,
BisqStatsComponent,
BsqAmountComponent,
LightweightChartsComponent,
BisqDashboardComponent,
BisqMarketComponent,
],
imports: [
BisqRoutingModule,

View file

@ -9,6 +9,8 @@ import { BisqExplorerComponent } from './bisq-explorer/bisq-explorer.component';
import { BisqAddressComponent } from './bisq-address/bisq-address.component';
import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
import { ApiDocsComponent } from '../components/api-docs/api-docs.component';
import { BisqDashboardComponent } from './bisq-dashboard/bisq-dashboard.component';
import { BisqMarketComponent } from './bisq-market/bisq-market.component';
const routes: Routes = [
{
@ -17,8 +19,16 @@ const routes: Routes = [
children: [
{
path: '',
component: BisqDashboardComponent,
},
{
path: 'transactions',
component: BisqTransactionsComponent
},
{
path: 'market/:pair',
component: BisqMarketComponent,
},
{
path: 'tx/:id',
component: BisqTransactionComponent

View file

@ -0,0 +1,68 @@
import { createChart, CrosshairMode } from 'lightweight-charts';
import { AfterViewInit, Component, ElementRef, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
@Component({
selector: 'app-lightweight-charts',
template: '<ng-component></ng-component>',
styleUrls: ['./lightweight-charts.component.scss']
})
export class LightweightChartsComponent implements AfterViewInit, OnChanges, OnDestroy {
@Input() data: any;
lineSeries: any;
chart: any;
constructor(
private element: ElementRef,
) {
this.chart = createChart(this.element.nativeElement, {
width: 1110,
height: 500,
layout: {
backgroundColor: '#000000',
textColor: '#d1d4dc',
},
crosshair: {
mode: CrosshairMode.Normal,
},
grid: {
vertLines: {
visible: true,
color: 'rgba(42, 46, 57, 0.5)',
},
horzLines: {
color: 'rgba(42, 46, 57, 0.5)',
},
},
});
this.lineSeries = this.chart.addCandlestickSeries();
}
ngAfterViewInit(): void {
/*
lineSeries.setData([
{ time: '2019-04-11', value: 80.01 },
{ time: '2019-04-12', value: 96.63 },
{ time: '2019-04-13', value: 76.64 },
{ time: '2019-04-14', value: 81.89 },
{ time: '2019-04-15', value: 74.43 },
{ time: '2019-04-16', value: 80.01 },
{ time: '2019-04-17', value: 96.63 },
{ time: '2019-04-18', value: 76.64 },
{ time: '2019-04-19', value: 81.89 },
{ time: '2019-04-20', value: 74.43 },
]);
*/
}
ngOnChanges() {
if (!this.data) {
return;
}
this.lineSeries.setData(this.data);
}
ngOnDestroy() {
this.chart.remove();
}
}

View file

@ -27,21 +27,21 @@
<div class="navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto pt-2 pb-2 pb-md-0 pt-md-0 {{ network.val }}">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.dashboard" title="Dashboard"></fa-icon></a>
</li>
<ng-template [ngIf]="network.val === 'bisq'" [ngIfElse]="notBisq">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<a class="nav-link" [routerLink]="['/bisq']" (click)="collapse()"><fa-icon [icon]="['fas', 'list']" [fixedWidth]="true" i18n-title="master-page.transactions" title="Transactions"></fa-icon></a>
<a class="nav-link" [routerLink]="['/bisq/transactions']" (click)="collapse()"><fa-icon [icon]="['fas', 'list']" [fixedWidth]="true" i18n-title="master-page.transactions" title="Transactions"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/bisq/blocks']" (click)="collapse()"><fa-icon [icon]="['fas', 'cubes']" [fixedWidth]="true" i18n-title="master-page.blocks" title="Blocks"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/bisq/stats']" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.stats" title="Stats"></fa-icon></a>
<a class="nav-link" [routerLink]="['/bisq/stats']" (click)="collapse()"><fa-icon [icon]="['fas', 'file-alt']" [fixedWidth]="true" i18n-title="master-page.stats" title="Stats"></fa-icon></a>
</li>
</ng-template>
<ng-template #notBisq>
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.dashboard" title="Dashboard"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/blocks' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cubes']" [fixedWidth]="true" i18n-title="master-page.blocks" title="Blocks"></fa-icon></a>
</li>

View file

@ -8,6 +8,7 @@
]
},
"array-type": false,
"forin": false,
"arrow-parens": false,
"arrow-return-shorthand": true,
"curly": true,