mirror of
https://github.com/mempool/mempool.git
synced 2025-02-24 06:47:52 +01:00
Bisq markets dashboard. Base views. WIP.
This commit is contained in:
parent
be0fd7c582
commit
eeb7447988
15 changed files with 325 additions and 5 deletions
27
frontend/package-lock.json
generated
27
frontend/package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
49
frontend/src/app/bisq/bisq-market/bisq-market.component.html
Normal file
49
frontend/src/app/bisq/bisq-market/bisq-market.component.html
Normal 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>
|
64
frontend/src/app/bisq/bisq-market/bisq-market.component.ts
Normal file
64
frontend/src/app/bisq/bisq-market/bisq-market.component.ts
Normal 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);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
]
|
||||
},
|
||||
"array-type": false,
|
||||
"forin": false,
|
||||
"arrow-parens": false,
|
||||
"arrow-return-shorthand": true,
|
||||
"curly": true,
|
||||
|
|
Loading…
Add table
Reference in a new issue