From 89b2e110837ae3d61478d8bf06ae538116f74332 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 12 Jul 2022 09:03:39 +0200 Subject: [PATCH 01/10] [Hashrate chart] Fix javascript error if difficulty array is empty --- .../hashrate-chart/hashrate-chart.component.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts index 385be0669..2a7cc07f2 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -109,7 +109,7 @@ export class HashrateChartComponent implements OnInit { while (hashIndex < data.hashrates.length) { diffFixed.push({ timestamp: data.hashrates[hashIndex].timestamp, - difficulty: data.difficulty[data.difficulty.length - 1].difficulty + difficulty: data.difficulty.length > 0 ? data.difficulty[data.difficulty.length - 1].difficulty : null }); ++hashIndex; } @@ -231,11 +231,15 @@ export class HashrateChartComponent implements OnInit { } else if (tick.seriesIndex === 1) { // Difficulty let difficultyPowerOfTen = hashratePowerOfTen; let difficulty = tick.data[1]; - if (this.isMobile()) { - difficultyPowerOfTen = selectPowerOfTen(tick.data[1]); - difficulty = Math.round(tick.data[1] / difficultyPowerOfTen.divider); + if (difficulty === null) { + difficultyString = `${tick.marker} ${tick.seriesName}: No data
`; + } else { + if (this.isMobile()) { + difficultyPowerOfTen = selectPowerOfTen(tick.data[1]); + difficulty = Math.round(tick.data[1] / difficultyPowerOfTen.divider); + } + difficultyString = `${tick.marker} ${tick.seriesName}: ${formatNumber(difficulty, this.locale, '1.2-2')} ${difficultyPowerOfTen.unit}
`; } - difficultyString = `${tick.marker} ${tick.seriesName}: ${formatNumber(difficulty, this.locale, '1.2-2')} ${difficultyPowerOfTen.unit}
`; } else if (tick.seriesIndex === 2) { // Hashrate MA let hashrate = tick.data[1]; if (this.isMobile()) { From d6158060e7d386b4463348ccca6730b7aed765c4 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sat, 16 Jul 2022 09:27:07 +0200 Subject: [PATCH 02/10] Ignore Kraken historical price without USD --- backend/src/tasks/price-feeds/kraken-api.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/tasks/price-feeds/kraken-api.ts b/backend/src/tasks/price-feeds/kraken-api.ts index ce76d62c2..ddb3c4f65 100644 --- a/backend/src/tasks/price-feeds/kraken-api.ts +++ b/backend/src/tasks/price-feeds/kraken-api.ts @@ -62,7 +62,7 @@ class KrakenApi implements PriceFeed { // CHF weekly price history goes back to timestamp 1575504000 (December 5, 2019) // AUD weekly price history goes back to timestamp 1591833600 (June 11, 2020) - const priceHistory: any = {}; // map: timestamp -> Prices + let priceHistory: any = {}; // map: timestamp -> Prices for (const currency of this.currencies) { const response = await query(this.urlHist.replace('{GRANULARITY}', '10080') + currency); @@ -83,6 +83,10 @@ class KrakenApi implements PriceFeed { } for (const time in priceHistory) { + if (priceHistory[time].USD === -1) { + delete priceHistory[time]; + continue; + } await PricesRepository.$savePrices(parseInt(time, 10), priceHistory[time]); } From 0c71e11cda1dd77c8b902497e36ed729d154322b Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sat, 16 Jul 2022 21:00:32 +0200 Subject: [PATCH 03/10] Move TV button to `/graphs/mempool` graph page --- .../app/components/master-page/master-page.component.html | 3 --- .../app/components/statistics/statistics.component.html | 7 +++++++ .../src/app/components/statistics/statistics.component.ts | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/components/master-page/master-page.component.html b/frontend/src/app/components/master-page/master-page.component.html index 564bd1b1e..a4979e00d 100644 --- a/frontend/src/app/components/master-page/master-page.component.html +++ b/frontend/src/app/components/master-page/master-page.component.html @@ -44,9 +44,6 @@ - diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html index ddeda4d80..dc168b877 100644 --- a/frontend/src/app/components/statistics/statistics.component.html +++ b/frontend/src/app/components/statistics/statistics.component.html @@ -11,6 +11,13 @@
+
+ +
diff --git a/frontend/src/app/graphs/graphs.routing.module.ts b/frontend/src/app/graphs/graphs.routing.module.ts index 20d7b6024..a853ad576 100644 --- a/frontend/src/app/graphs/graphs.routing.module.ts +++ b/frontend/src/app/graphs/graphs.routing.module.ts @@ -21,6 +21,7 @@ import { DashboardComponent } from '../dashboard/dashboard.component'; import { NodesNetworksChartComponent } from '../lightning/nodes-networks-chart/nodes-networks-chart.component'; import { LightningStatisticsChartComponent } from '../lightning/statistics-chart/lightning-statistics-chart.component'; import { NodesPerISPChartComponent } from '../lightning/nodes-per-isp-chart/nodes-per-isp-chart.component'; +import { NodesPerCountryChartComponent } from '../lightning/nodes-per-country-chart/nodes-per-country-chart.component'; const browserWindow = window || {}; // @ts-ignore @@ -104,6 +105,10 @@ const routes: Routes = [ path: 'lightning/nodes-per-isp', component: NodesPerISPChartComponent, }, + { + path: 'lightning/nodes-per-country', + component: NodesPerCountryChartComponent, + }, { path: '', redirectTo: 'mempool', diff --git a/frontend/src/app/lightning/lightning.module.ts b/frontend/src/app/lightning/lightning.module.ts index b5fc0da14..4e2633b65 100644 --- a/frontend/src/app/lightning/lightning.module.ts +++ b/frontend/src/app/lightning/lightning.module.ts @@ -21,6 +21,7 @@ import { ChannelsStatisticsComponent } from './channels-statistics/channels-stat import { NodesPerISPChartComponent } from './nodes-per-isp-chart/nodes-per-isp-chart.component'; import { NodesPerCountry } from './nodes-per-country/nodes-per-country.component'; import { NodesPerISP } from './nodes-per-isp/nodes-per-isp.component'; +import { NodesPerCountryChartComponent } from '../lightning/nodes-per-country-chart/nodes-per-country-chart.component'; @NgModule({ declarations: [ LightningDashboardComponent, @@ -39,6 +40,7 @@ import { NodesPerISP } from './nodes-per-isp/nodes-per-isp.component'; NodesPerISPChartComponent, NodesPerCountry, NodesPerISP, + NodesPerCountryChartComponent, ], imports: [ CommonModule, diff --git a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html new file mode 100644 index 000000000..96735f4df --- /dev/null +++ b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html @@ -0,0 +1,52 @@ +
+ +
+
+ Lightning nodes per country + +
+ (Tor nodes excluded) +
+ +
+
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + +
RankNameNodesCapacity
{{ country.rank }}{{ country.name.en }}{{ country.count }} + + + {{ country.capacity | amountShortener: 1 }} + sats + +
+
+ +
\ No newline at end of file diff --git a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.scss b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.scss new file mode 100644 index 000000000..c2c94cac0 --- /dev/null +++ b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.scss @@ -0,0 +1,81 @@ +.sats { + color: #ffffff66; + font-size: 12px; + top: 0px; +} + +.card-header { + border-bottom: 0; + font-size: 18px; + @media (min-width: 465px) { + font-size: 20px; + } +} + +.full-container { + padding: 0px 15px; + width: 100%; + height: calc(100% - 140px); + @media (max-width: 992px) { + height: calc(100% - 190px); + }; + @media (max-width: 575px) { + height: calc(100% - 230px); + }; +} + +.chart { + max-height: 400px; + @media (max-width: 767.98px) { + max-height: 230px; + margin-top: -35px; + } +} + +.bottom-padding { + @media (max-width: 992px) { + padding-bottom: 65px + }; + @media (max-width: 576px) { + padding-bottom: 65px + }; +} + +.rank { + width: 20%; + @media (max-width: 576px) { + display: none + } +} + +.name { + width: 20%; + @media (max-width: 576px) { + width: 80%; + max-width: 150px; + padding-left: 0; + padding-right: 0; + } +} + +.share { + width: 20%; + @media (max-width: 576px) { + display: none + } +} + +.nodes { + width: 20%; + @media (max-width: 576px) { + width: 10%; + } +} + +.capacity { + width: 20%; + @media (max-width: 576px) { + width: 10%; + max-width: 100px; + } +} diff --git a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts new file mode 100644 index 000000000..7e447933d --- /dev/null +++ b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts @@ -0,0 +1,231 @@ +import { ChangeDetectionStrategy, Component, OnInit, HostBinding, NgZone } from '@angular/core'; +import { Router } from '@angular/router'; +import { EChartsOption, PieSeriesOption } from 'echarts'; +import { map, Observable, share, tap } from 'rxjs'; +import { chartColors } from 'src/app/app.constants'; +import { ApiService } from 'src/app/services/api.service'; +import { SeoService } from 'src/app/services/seo.service'; +import { StateService } from 'src/app/services/state.service'; +import { download } from 'src/app/shared/graphs.utils'; +import { AmountShortenerPipe } from 'src/app/shared/pipes/amount-shortener.pipe'; +import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe'; + +@Component({ + selector: 'app-nodes-per-country-chart', + templateUrl: './nodes-per-country-chart.component.html', + styleUrls: ['./nodes-per-country-chart.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class NodesPerCountryChartComponent implements OnInit { + miningWindowPreference: string; + + isLoading = true; + chartOptions: EChartsOption = {}; + chartInitOptions = { + renderer: 'svg', + }; + timespan = ''; + chartInstance: any = undefined; + + @HostBinding('attr.dir') dir = 'ltr'; + + nodesPerCountryObservable$: Observable; + + constructor( + private apiService: ApiService, + private seoService: SeoService, + private amountShortenerPipe: AmountShortenerPipe, + private zone: NgZone, + private stateService: StateService, + private router: Router, + ) { + } + + ngOnInit(): void { + this.seoService.setTitle($localize`Lightning nodes per country`); + + this.nodesPerCountryObservable$ = this.apiService.getNodesPerCountry() + .pipe( + tap(data => { + this.isLoading = false; + this.prepareChartOptions(data); + }), + map(data => { + for (let i = 0; i < data.length; ++i) { + data[i].rank = i + 1; + } + return data.slice(0, 100); + }), + share() + ); + } + + generateChartSerieData(country) { + const shareThreshold = this.isMobile() ? 2 : 1; + const data: object[] = []; + let totalShareOther = 0; + let totalNodeOther = 0; + + let edgeDistance: string | number = '10%'; + if (this.isMobile()) { + edgeDistance = 0; + } + + country.forEach((country) => { + if (country.share < shareThreshold) { + totalShareOther += country.share; + totalNodeOther += country.count; + return; + } + data.push({ + value: country.share, + name: country.name.en + (this.isMobile() ? `` : ` (${country.share}%)`), + label: { + overflow: 'truncate', + color: '#b1b1b1', + alignTo: 'edge', + edgeDistance: edgeDistance, + }, + tooltip: { + show: !this.isMobile(), + backgroundColor: 'rgba(17, 19, 31, 1)', + borderRadius: 4, + shadowColor: 'rgba(0, 0, 0, 0.5)', + textStyle: { + color: '#b1b1b1', + }, + borderColor: '#000', + formatter: () => { + return `${country.name.en} (${country.share}%)
` + + $localize`${country.count.toString()} nodes
` + + $localize`${this.amountShortenerPipe.transform(country.capacity / 100000000, 2)} BTC capacity` + ; + } + }, + data: country.iso, + } as PieSeriesOption); + }); + + // 'Other' + data.push({ + itemStyle: { + color: 'grey', + }, + value: totalShareOther, + name: 'Other' + (this.isMobile() ? `` : ` (${totalShareOther.toFixed(2)}%)`), + label: { + overflow: 'truncate', + color: '#b1b1b1', + alignTo: 'edge', + edgeDistance: edgeDistance + }, + tooltip: { + backgroundColor: 'rgba(17, 19, 31, 1)', + borderRadius: 4, + shadowColor: 'rgba(0, 0, 0, 0.5)', + textStyle: { + color: '#b1b1b1', + }, + borderColor: '#000', + formatter: () => { + return `${'Other'} (${totalShareOther.toFixed(2)}%)
` + + totalNodeOther.toString() + ` nodes`; + } + }, + } as PieSeriesOption); + + return data; + } + + prepareChartOptions(country) { + let pieSize = ['20%', '80%']; // Desktop + if (this.isMobile()) { + pieSize = ['15%', '60%']; + } + + this.chartOptions = { + animation: false, + color: chartColors, + tooltip: { + trigger: 'item', + textStyle: { + align: 'left', + } + }, + series: [ + { + zlevel: 0, + minShowLabelAngle: 3.6, + name: 'Mining pool', + type: 'pie', + radius: pieSize, + data: this.generateChartSerieData(country), + labelLine: { + lineStyle: { + width: 2, + }, + length: this.isMobile() ? 1 : 20, + length2: this.isMobile() ? 1 : undefined, + }, + label: { + fontSize: 14, + }, + itemStyle: { + borderRadius: 1, + borderWidth: 1, + borderColor: '#000', + }, + emphasis: { + itemStyle: { + shadowBlur: 40, + shadowColor: 'rgba(0, 0, 0, 0.75)', + }, + labelLine: { + lineStyle: { + width: 4, + } + } + } + } + ], + }; + } + + isMobile() { + return (window.innerWidth <= 767.98); + } + + onChartInit(ec) { + if (this.chartInstance !== undefined) { + return; + } + this.chartInstance = ec; + + this.chartInstance.on('click', (e) => { + if (e.data.data === 9999) { // "Other" + return; + } + this.zone.run(() => { + const url = new RelativeUrlPipe(this.stateService).transform(`/lightning/nodes/country/${e.data.data}`); + this.router.navigate([url]); + }); + }); + } + + onSaveChart() { + const now = new Date(); + this.chartOptions.backgroundColor = '#11131f'; + this.chartInstance.setOption(this.chartOptions); + download(this.chartInstance.getDataURL({ + pixelRatio: 2, + excludeComponents: ['dataZoom'], + }), `lightning-nodes-per-country-${Math.round(now.getTime() / 1000)}.svg`); + this.chartOptions.backgroundColor = 'none'; + this.chartInstance.setOption(this.chartOptions); + } + + isEllipsisActive(e) { + return (e.offsetWidth < e.scrollWidth); + } +} + diff --git a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.scss b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.scss index 02b47e8be..25e4cf7f3 100644 --- a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.scss +++ b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.scss @@ -13,7 +13,6 @@ width: 30%; max-width: 400px; padding-right: 70px; - @media (max-width: 576px) { width: 50%; max-width: 150px; @@ -23,7 +22,6 @@ .timestamp-first { width: 20%; - @media (max-width: 576px) { display: none } @@ -31,7 +29,6 @@ .timestamp-update { width: 16%; - @media (max-width: 576px) { display: none } @@ -39,7 +36,6 @@ .capacity { width: 10%; - @media (max-width: 576px) { width: 25%; } @@ -47,7 +43,6 @@ .channels { width: 10%; - @media (max-width: 576px) { width: 25%; } @@ -55,7 +50,6 @@ .city { max-width: 150px; - @media (max-width: 576px) { display: none } diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 0f68b071b..b850e893e 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -262,4 +262,8 @@ export class ApiService { getNodeForISP$(isp: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/isp/' + isp); } + + getNodesPerCountry(): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/countries'); + } } From b4bcd84a539aa052cb2d063d3f6a240e103a0922 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sun, 17 Jul 2022 23:14:01 +0200 Subject: [PATCH 06/10] Add link to nodes per country list component --- .../nodes-per-country-chart.component.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html index 96735f4df..5c3acec89 100644 --- a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html +++ b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html @@ -34,7 +34,9 @@ {{ country.rank }} - {{ country.name.en }} + + {{ country.name.en }} + {{ country.share }}% {{ country.count }} From 420ff16c2b26b8db06bbb8ea6cf4c9f0c87aeb28 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sun, 17 Jul 2022 23:28:00 +0200 Subject: [PATCH 07/10] Make sure "other" is not clickable --- .../nodes-per-country-chart.component.html | 8 ++++---- .../nodes-per-country-chart.component.ts | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html index 5c3acec89..f327a7147 100644 --- a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html +++ b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html @@ -24,20 +24,20 @@ - + - + - + - + diff --git a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts index cee350a34..c6a640015 100644 --- a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts +++ b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts @@ -9,6 +9,7 @@ import { StateService } from 'src/app/services/state.service'; import { download } from 'src/app/shared/graphs.utils'; import { AmountShortenerPipe } from 'src/app/shared/pipes/amount-shortener.pipe'; import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe'; +import { getFlagEmoji } from 'src/app/shared/graphs.utils'; @Component({ selector: 'app-nodes-per-country-chart', @@ -50,6 +51,7 @@ export class NodesPerCountryChartComponent implements OnInit { for (let i = 0; i < data.length; ++i) { data[i].rank = i + 1; data[i].iso = data[i].iso.toLowerCase(); + data[i].flag = getFlagEmoji(data[i].iso); } return data.slice(0, 100); }), diff --git a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html index 82280bdab..2896b4544 100644 --- a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html +++ b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html @@ -1,5 +1,8 @@
-

Lightning nodes in {{ country }}

+

+ Lightning nodes in {{ country?.name }} + {{ country?.flag }} +

RankRank Name Nodes Capacity
{{ country.rank }}{{ country.rank }} {{ country.name.en }} {{ country.count }} diff --git a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts index 7e447933d..df8c661ef 100644 --- a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts +++ b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts @@ -130,8 +130,9 @@ export class NodesPerCountryChartComponent implements OnInit { formatter: () => { return `${'Other'} (${totalShareOther.toFixed(2)}%)
` + totalNodeOther.toString() + ` nodes`; - } + }, }, + data: 9999 as any } as PieSeriesOption); return data; From f16076b4011d2c5b176b59f420b8881e88dda511 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Mon, 18 Jul 2022 00:06:48 +0200 Subject: [PATCH 08/10] Keep county ISO code in lower case in url --- .../nodes-per-country-chart.component.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts index df8c661ef..cee350a34 100644 --- a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts +++ b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts @@ -46,16 +46,17 @@ export class NodesPerCountryChartComponent implements OnInit { this.nodesPerCountryObservable$ = this.apiService.getNodesPerCountry() .pipe( - tap(data => { - this.isLoading = false; - this.prepareChartOptions(data); - }), map(data => { for (let i = 0; i < data.length; ++i) { data[i].rank = i + 1; + data[i].iso = data[i].iso.toLowerCase(); } return data.slice(0, 100); }), + tap(data => { + this.isLoading = false; + this.prepareChartOptions(data); + }), share() ); } From ccdeb108eef3e3154c6e3382b491e5f5c5fe8b09 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Mon, 18 Jul 2022 00:55:47 +0200 Subject: [PATCH 09/10] Show country flag emoji --- .../nodes-per-country-chart.component.html | 6 +++++- .../nodes-per-country-chart.component.ts | 2 ++ .../nodes-per-country/nodes-per-country.component.html | 5 ++++- .../nodes-per-country/nodes-per-country.component.ts | 10 +++++++--- frontend/src/app/shared/graphs.utils.ts | 8 ++++++++ 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html index f327a7147..abc0e306c 100644 --- a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html +++ b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html @@ -35,7 +35,11 @@
{{ country.rank }} - {{ country.name.en }} +
+ {{ country.flag }} +   + {{ country.name.en }} +
{{ country.count }}
diff --git a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts index e353d1361..a247baadf 100644 --- a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts +++ b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts @@ -3,6 +3,7 @@ import { ActivatedRoute } from '@angular/router'; import { map, Observable } from 'rxjs'; import { ApiService } from 'src/app/services/api.service'; import { SeoService } from 'src/app/services/seo.service'; +import { getFlagEmoji } from 'src/app/shared/graphs.utils'; @Component({ selector: 'app-nodes-per-country', @@ -12,7 +13,7 @@ import { SeoService } from 'src/app/services/seo.service'; }) export class NodesPerCountry implements OnInit { nodes$: Observable; - country: string; + country: {name: string, flag: string}; constructor( private apiService: ApiService, @@ -24,8 +25,11 @@ export class NodesPerCountry implements OnInit { this.nodes$ = this.apiService.getNodeForCountry$(this.route.snapshot.params.country) .pipe( map(response => { - this.country = response.country.en - this.seoService.setTitle($localize`Lightning nodes in ${this.country}`); + this.country = { + name: response.country.en, + flag: getFlagEmoji(this.route.snapshot.params.country) + }; + this.seoService.setTitle($localize`Lightning nodes in ${this.country.name}`); return response.nodes; }) ); diff --git a/frontend/src/app/shared/graphs.utils.ts b/frontend/src/app/shared/graphs.utils.ts index 2e103ecda..6909e6fac 100644 --- a/frontend/src/app/shared/graphs.utils.ts +++ b/frontend/src/app/shared/graphs.utils.ts @@ -90,3 +90,11 @@ export function detectWebGL() { const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); return (gl && gl instanceof WebGLRenderingContext); } + +export function getFlagEmoji(countryCode) { + const codePoints = countryCode + .toUpperCase() + .split('') + .map(char => 127397 + char.charCodeAt()); + return String.fromCodePoint(...codePoints); +} From 681d9db900d1e1d35087f9e8f62630439addfdd7 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Mon, 18 Jul 2022 01:11:20 +0200 Subject: [PATCH 10/10] Fix error 500 for `Isle of Man` nodes list --- backend/src/api/explorer/nodes.api.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/api/explorer/nodes.api.ts b/backend/src/api/explorer/nodes.api.ts index 831eeba45..a14f7336f 100644 --- a/backend/src/api/explorer/nodes.api.ts +++ b/backend/src/api/explorer/nodes.api.ts @@ -129,7 +129,7 @@ class NodesApi { public async $getNodesPerCountry(countryId: string) { try { const query = ` - SELECT DISTINCT node_stats.public_key, node_stats.capacity, node_stats.channels, nodes.alias, + SELECT node_stats.public_key, node_stats.capacity, node_stats.channels, nodes.alias, UNIX_TIMESTAMP(nodes.first_seen) as first_seen, UNIX_TIMESTAMP(nodes.updated_at) as updated_at, geo_names_city.names as city FROM node_stats @@ -139,8 +139,8 @@ class NodesApi { GROUP BY public_key ) as b ON b.public_key = node_stats.public_key AND b.last_added = node_stats.added JOIN nodes ON nodes.public_key = node_stats.public_key - JOIN geo_names geo_names_country ON geo_names_country.id = nodes.country_id - LEFT JOIN geo_names geo_names_city ON geo_names_city.id = nodes.city_id + JOIN geo_names geo_names_country ON geo_names_country.id = nodes.country_id AND geo_names_country.type = 'country' + LEFT JOIN geo_names geo_names_city ON geo_names_city.id = nodes.city_id AND geo_names_city.type = 'city' WHERE geo_names_country.id = ? ORDER BY capacity DESC `;