Merge branch 'master' into refactor-preview-routing

This commit is contained in:
wiz 2022-09-12 04:18:14 +09:00 committed by GitHub
commit da8044c073
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 266 additions and 15 deletions

View File

@ -8,10 +8,12 @@ import { LightningApiService } from './lightning-api.service';
import { NodePreviewComponent } from './node/node-preview.component';
import { LightningPreviewsRoutingModule } from './lightning-previews.routing.module';
import { ChannelPreviewComponent } from './channel/channel-preview.component';
import { NodesPerISPPreview } from './nodes-per-isp/nodes-per-isp-preview.component';
@NgModule({
declarations: [
NodePreviewComponent,
ChannelPreviewComponent,
NodesPerISPPreview,
],
imports: [
CommonModule,

View File

@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { NodePreviewComponent } from './node/node-preview.component';
import { ChannelPreviewComponent } from './channel/channel-preview.component';
import { NodesPerISPPreview } from './nodes-per-isp/nodes-per-isp-preview.component';
const routes: Routes = [
{
@ -12,6 +13,10 @@ const routes: Routes = [
path: 'channel/:short_id',
component: ChannelPreviewComponent,
},
{
path: 'nodes/isp/:isp',
component: NodesPerISPPreview,
},
{
path: '**',
redirectTo: ''

View File

@ -43,6 +43,13 @@
<ng-template #loadingReward>
<div class="fee-estimation-container loading-container">
<div class="item">
<h5 class="card-title" i18n="lightning.capacity">Capacity</h5>
<div class="card-text">
<div class="skeleton-loader"></div>
<div class="skeleton-loader"></div>
</div>
</div>
<div class="item">
<h5 class="card-title" i18n="lightning.nodes">Nodes</h5>
<div class="card-text">
@ -57,12 +64,5 @@
<div class="skeleton-loader"></div>
</div>
</div>
<div class="item">
<h5 class="card-title" i18n="lightning.average-channels">Average Channel</h5>
<div class="card-text">
<div class="skeleton-loader"></div>
<div class="skeleton-loader"></div>
</div>
</div>
</div>
</ng-template>

View File

@ -1,4 +1,4 @@
<div class="full-container" [class]="widget ? 'widget' : ''">
<div class="full-container" [class]="widget ? 'widget' : ''" [class.fit-container]="fitContainer">
<div *ngIf="!widget" class="card-header">
<div class="d-flex d-md-block align-items-baseline" style="margin-bottom: -5px">
@ -8,7 +8,7 @@
</div>
<div *ngIf="observable$ | async" class="chart" [class]="widget ? 'widget' : ''" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
(chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
</div>
</div>

View File

@ -21,6 +21,17 @@
height: 240px;
padding: 0px;
}
.full-container.fit-container {
margin: 0;
padding: 0;
height: 100%;
min-height: 100px;
.chart {
padding: 0;
min-height: 100px;
}
}
.chart {
width: 100%;

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, OnDestroy, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Inject, Input, Output, EventEmitter, LOCALE_ID, NgZone, OnDestroy, OnInit, OnChanges } from '@angular/core';
import { SeoService } from 'src/app/services/seo.service';
import { ApiService } from 'src/app/services/api.service';
import { Observable, tap, zip } from 'rxjs';
import { Observable, BehaviorSubject, switchMap, tap, combineLatest } from 'rxjs';
import { AssetsService } from 'src/app/services/assets.service';
import { EChartsOption, registerMap } from 'echarts';
import { lerpColor } from 'src/app/shared/graphs.utils';
@ -17,11 +17,14 @@ import { getFlagEmoji } from 'src/app/shared/common.utils';
styleUrls: ['./nodes-map.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NodesMap implements OnInit {
export class NodesMap implements OnInit, OnChanges {
@Input() widget: boolean = false;
@Input() nodes: any[] | undefined = undefined;
@Input() type: 'none' | 'isp' | 'country' = 'none';
@Input() fitContainer = false;
@Output() readyEvent = new EventEmitter();
inputNodes$: BehaviorSubject<any>;
nodes$: Observable<any>;
observable$: Observable<any>;
chartInstance = undefined;
@ -45,9 +48,17 @@ export class NodesMap implements OnInit {
ngOnInit(): void {
this.seoService.setTitle($localize`Lightning nodes world map`);
this.observable$ = zip(
if (!this.inputNodes$) {
this.inputNodes$ = new BehaviorSubject(this.nodes);
}
this.nodes$ = this.inputNodes$.pipe(
switchMap((nodes) => nodes ? [nodes] : this.apiService.getWorldNodes$())
);
this.observable$ = combineLatest(
this.assetsService.getWorldMapJson$,
this.nodes ? [this.nodes] : this.apiService.getWorldNodes$()
this.nodes$
).pipe(tap((data) => {
registerMap('world', data[0]);
@ -110,6 +121,16 @@ export class NodesMap implements OnInit {
}));
}
ngOnChanges(changes): void {
if (changes.nodes) {
if (!this.inputNodes$) {
this.inputNodes$ = new BehaviorSubject(changes.nodes.currentValue);
} else {
this.inputNodes$.next(changes.nodes.currentValue);
}
}
}
prepareChartOptions(nodes, maxLiquidity, mapCenter, mapZoom) {
let title: object;
if (nodes.length === 0) {
@ -224,4 +245,8 @@ export class NodesMap implements OnInit {
this.chartInstance.resize();
});
}
onChartFinished(e) {
this.readyEvent.emit();
}
}

View File

@ -0,0 +1,63 @@
<div class="box preview-box" *ngIf="(nodes$ | async) as ispNodes">
<app-preview-title>
<span i18n="lightning.node">lightning ISP</span>
</app-preview-title>
<div class="row d-flex justify-content-between full-width-row">
<div class="title-wrapper">
<h1 class="title">{{ isp?.name }}</h1>
<a class="subtitle" [routerLink]="['/lightning/nodes/isp/' | relativeUrl, isp?.id]">
ASN {{ isp?.id }}
</a>
</div>
<div class="logo-wrapper">
</div>
</div>
<div class="row">
<div class="col-md">
<table class="table table-borderless table-striped table-fixed">
<col span="1" width="250px">
<tbody>
<tr>
<td i18n="lightning.node-count">Nodes</td>
<td>{{ ispNodes.nodes.length }}</td>
</tr>
<tr>
<td i18n="lightning.liquidity">Liquidity</td>
<td>
<app-amount *ngIf="ispNodes.sumLiquidity > 100000000; else smallnode" [satoshis]="ispNodes.sumLiquidity" [digitsInfo]="'1.2-2'" [noFiat]="false"></app-amount>
<ng-template #smallnode>
<app-sats [satoshis]="ispNodes.sumLiquidity" digitsInfo="1.0-2"></app-sats>
</ng-template>
</td>
</tr>
<tr>
<td i18n="lightning.channels">Channels</td>
<td>{{ ispNodes.sumChannels }}</td>
</tr>
<tr>
<td i18n="lightning.top-country">Top country</td>
<td class="text-truncate">
<span class="">{{ ispNodes.topCountry.country }} {{ ispNodes.topCountry.flag }}</span>
</td>
</tr>
<tr>
<td i18n="lightning.top-node">Top node</td>
<td class="text-truncate">
{{ ispNodes.nodes[0].alias }}
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md map-col">
<app-nodes-map [widget]="true" [nodes]="ispNodes.nodes" type="isp" [fitContainer]="true" (readyEvent)="onMapReady()"></app-nodes-map>
</div>
</div>
</div>
<ng-template [ngIf]="error">
<div class="text-center">
<span i18n="error.general-loading-data">Error loading data.</span>
</div>
</ng-template>

View File

@ -0,0 +1,31 @@
.table {
font-size: 32px;
margin-top: 0px;
}
.map-col {
flex-grow: 0;
flex-shrink: 0;
width: 470px;
height: 360px;
min-width: 470px;
min-height: 360px;
max-height: 360px;
padding: 0;
background: #181b2d;
overflow: hidden;
margin-top: 0;
}
.row {
margin-right: 0;
}
.full-width-row {
padding-left: 15px;
flex-wrap: nowrap;
}
::ng-deep .symbol {
font-size: 24px;
}

View File

@ -0,0 +1,103 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { catchError, map, switchMap, Observable, share, of } from 'rxjs';
import { ApiService } from 'src/app/services/api.service';
import { SeoService } from 'src/app/services/seo.service';
import { OpenGraphService } from 'src/app/services/opengraph.service';
import { getFlagEmoji } from 'src/app/shared/common.utils';
import { GeolocationData } from 'src/app/shared/components/geolocation/geolocation.component';
@Component({
selector: 'app-nodes-per-isp-preview',
templateUrl: './nodes-per-isp-preview.component.html',
styleUrls: ['./nodes-per-isp-preview.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NodesPerISPPreview implements OnInit {
nodes$: Observable<any>;
isp: {name: string, id: number};
id: string;
error: Error;
constructor(
private apiService: ApiService,
private seoService: SeoService,
private openGraphService: OpenGraphService,
private route: ActivatedRoute,
) { }
ngOnInit(): void {
this.nodes$ = this.route.paramMap
.pipe(
switchMap((params: ParamMap) => {
this.id = params.get('isp');
this.isp = null;
this.openGraphService.waitFor('isp-map-' + this.id);
this.openGraphService.waitFor('isp-data-' + this.id);
return this.apiService.getNodeForISP$(params.get('isp'));
}),
map(response => {
this.isp = {
name: response.isp,
id: this.route.snapshot.params.isp.split(',').join(', ')
};
this.seoService.setTitle($localize`Lightning nodes on ISP: ${response.isp} [AS${this.route.snapshot.params.isp}]`);
for (const i in response.nodes) {
response.nodes[i].geolocation = <GeolocationData>{
country: response.nodes[i].country?.en,
city: response.nodes[i].city?.en,
subdivision: response.nodes[i].subdivision?.en,
iso: response.nodes[i].iso_code,
};
}
const sumLiquidity = response.nodes.reduce((partialSum, a) => partialSum + a.capacity, 0);
const sumChannels = response.nodes.reduce((partialSum, a) => partialSum + a.channels, 0);
const countries = {};
const topCountry = {
count: 0,
country: '',
iso: '',
flag: '',
};
for (const node of response.nodes) {
if (!node.geolocation.iso) {
continue;
}
countries[node.geolocation.iso] = countries[node.geolocation.iso] ?? 0 + 1;
if (countries[node.geolocation.iso] > topCountry.count) {
topCountry.count = countries[node.geolocation.iso];
topCountry.country = node.geolocation.country;
topCountry.iso = node.geolocation.iso;
}
}
topCountry.flag = getFlagEmoji(topCountry.iso);
this.openGraphService.waitOver('isp-data-' + this.id);
return {
nodes: response.nodes,
sumLiquidity: sumLiquidity,
sumChannels: sumChannels,
topCountry: topCountry,
};
}),
catchError(err => {
this.error = err;
this.openGraphService.fail('isp-map-' + this.id);
this.openGraphService.fail('isp-data-' + this.id);
return of({
nodes: [],
sumLiquidity: 0,
sumChannels: 0,
topCountry: {},
});
})
);
}
onMapReady() {
this.openGraphService.waitOver('isp-map-' + this.id);
}
}

View File

@ -46,6 +46,17 @@ const routes = {
return `Lightning Channel: ${path[0]}`;
}
},
nodes: {
routes: {
isp: {
render: true,
params: 1,
getTitle(path) {
return `Lightning ISP: ${path[0]}`;
}
}
}
}
}
},
mining: {