Merge branch 'master' into nymkappa/bugfix/show-hybrid-nodes-chart
This commit is contained in:
commit
cce82c12f0
@ -1,5 +1,15 @@
|
|||||||
|
<ng-template [ngIf]="button" [ngIfElse]="btnLink">
|
||||||
|
<button #btn [attr.data-clipboard-text]="text" [class]="class" type="button" [disabled]="text === ''">
|
||||||
|
<span #buttonWrapper [attr.data-tlite]="copiedMessage" style="position: relative;top: -2px;left: 1px;">
|
||||||
|
<app-svg-images name="clippy" [width]="size === 'small' ? '10' : '13'" viewBox="0 0 1000 1000"></app-svg-images>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #btnLink>
|
||||||
<span #buttonWrapper [attr.data-tlite]="copiedMessage" style="position: relative;">
|
<span #buttonWrapper [attr.data-tlite]="copiedMessage" style="position: relative;">
|
||||||
<button #btn class="btn btn-sm btn-link pt-0 {{ leftPadding ? 'padding' : '' }}" [attr.data-clipboard-text]="text">
|
<button #btn class="btn btn-sm btn-link pt-0 {{ leftPadding ? 'padding' : '' }}" [attr.data-clipboard-text]="text">
|
||||||
<app-svg-images name="clippy" [width]="size === 'small' ? '10' : '13'" viewBox="0 0 1000 1000"></app-svg-images>
|
<app-svg-images name="clippy" [width]="size === 'small' ? '10' : '13'" viewBox="0 0 1000 1000"></app-svg-images>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
@ -11,6 +11,8 @@ import * as tlite from 'tlite';
|
|||||||
export class ClipboardComponent implements AfterViewInit {
|
export class ClipboardComponent implements AfterViewInit {
|
||||||
@ViewChild('btn') btn: ElementRef;
|
@ViewChild('btn') btn: ElementRef;
|
||||||
@ViewChild('buttonWrapper') buttonWrapper: ElementRef;
|
@ViewChild('buttonWrapper') buttonWrapper: ElementRef;
|
||||||
|
@Input() button = false;
|
||||||
|
@Input() class = 'btn btn-secondary ml-1';
|
||||||
@Input() size: 'small' | 'normal' = 'normal';
|
@Input() size: 'small' | 'normal' = 'normal';
|
||||||
@Input() text: string;
|
@Input() text: string;
|
||||||
@Input() leftPadding = true;
|
@Input() leftPadding = true;
|
||||||
|
@ -35,10 +35,10 @@
|
|||||||
<path d="M 464.598 86.568 C 452.881 86.568 444.371 78.058 444.371 66.32 C 444.371 54.644 452.881 46.175 464.598 46.175 C 476.254 46.175 484.723 54.644 484.723 66.32 C 484.723 78.058 476.254 86.568 464.598 86.568 Z M 464.598 53.102 C 457.198 53.102 452.018 58.529 452.018 66.32 C 452.018 74.111 457.198 79.538 464.598 79.538 C 471.937 79.538 477.076 74.111 477.076 66.32 C 477.076 58.529 471.958 53.102 464.598 53.102 Z" fill="white"/>
|
<path d="M 464.598 86.568 C 452.881 86.568 444.371 78.058 444.371 66.32 C 444.371 54.644 452.881 46.175 464.598 46.175 C 476.254 46.175 484.723 54.644 484.723 66.32 C 484.723 78.058 476.254 86.568 464.598 86.568 Z M 464.598 53.102 C 457.198 53.102 452.018 58.529 452.018 66.32 C 452.018 74.111 457.198 79.538 464.598 79.538 C 471.937 79.538 477.076 74.111 477.076 66.32 C 477.076 58.529 471.958 53.102 464.598 53.102 Z" fill="white"/>
|
||||||
<path d="M 499.996 31.148 L 492.391 31.148 L 492.391 86.198 L 499.996 86.198 L 499.996 31.148 Z" fill="white"/>
|
<path d="M 499.996 31.148 L 492.391 31.148 L 492.391 86.198 L 499.996 86.198 L 499.996 31.148 Z" fill="white"/>
|
||||||
<path d="M124.706 110.25C124.706 118.849 117.772 125.791 109.183 125.791H15.5236C6.93387 125.791 0 118.849 0 110.25V16.4837C0 7.88416 6.98561 0.942383 15.5236 0.942383H109.183C117.772 0.942383 124.706 7.88416 124.706 16.4837V110.25Z" fill="#2E3349"/>
|
<path d="M124.706 110.25C124.706 118.849 117.772 125.791 109.183 125.791H15.5236C6.93387 125.791 0 118.849 0 110.25V16.4837C0 7.88416 6.98561 0.942383 15.5236 0.942383H109.183C117.772 0.942383 124.706 7.88416 124.706 16.4837V110.25Z" fill="#2E3349"/>
|
||||||
<path d="M0 63.5225V110.25C0 118.849 6.98561 125.791 15.5753 125.791H109.183C117.772 125.791 124.758 118.849 124.758 110.25V63.5225H0Z" fill="url('#paint0_linear')"/>
|
<path d="M0 63.5225V110.25C0 118.849 6.98561 125.791 15.5753 125.791H109.183C117.772 125.791 124.758 118.849 124.758 110.25V63.5225H0Z" [attr.fill]="'url(#paint0_linear' + randomId + ')'"/>
|
||||||
<path opacity="0.3" d="M109.909 109.11C109.909 111.026 108.615 112.581 107.011 112.581H90.8665C89.2624 112.581 87.9688 111.026 87.9688 109.11V17.6232C87.9688 15.7065 89.2624 14.1523 90.8665 14.1523H107.011C108.615 14.1523 109.909 15.7065 109.909 17.6232V109.11Z" fill="white"/>
|
<path opacity="0.3" d="M109.909 109.11C109.909 111.026 108.615 112.581 107.011 112.581H90.8665C89.2624 112.581 87.9688 111.026 87.9688 109.11V17.6232C87.9688 15.7065 89.2624 14.1523 90.8665 14.1523H107.011C108.615 14.1523 109.909 15.7065 109.909 17.6232V109.11Z" fill="white"/>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="paint0_linear" x1="62.3768" y1="36.3949" x2="62.3768" y2="156.837" gradientUnits="userSpaceOnUse">
|
<linearGradient [id]="'paint0_linear' + randomId" x1="62.3768" y1="36.3949" x2="62.3768" y2="156.837" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#AE61FF"/>
|
<stop stop-color="#AE61FF"/>
|
||||||
<stop offset="1" stop-color="#13EFD8"/>
|
<stop offset="1" stop-color="#13EFD8"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
@ -191,12 +191,20 @@
|
|||||||
<br>
|
<br>
|
||||||
|
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h2 i18n="transaction.diagram|Transaction diagram">Diagram</h2>
|
<h2 i18n="transaction.flow|Transaction flow">Flow</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="graph-container" #graphContainer>
|
<div class="graph-container" #graphContainer>
|
||||||
<tx-bowtie-graph [tx]="tx" [width]="graphWidth" [height]="graphExpanded ? (maxInOut * 15) : graphHeight" [maxStrands]="graphExpanded ? maxInOut : 24" [network]="network" [tooltip]="true"></tx-bowtie-graph>
|
<tx-bowtie-graph
|
||||||
|
[tx]="tx"
|
||||||
|
[width]="graphWidth"
|
||||||
|
[height]="graphExpanded ? (maxInOut * 15) : graphHeight"
|
||||||
|
[lineLimit]="inOutLimit"
|
||||||
|
[maxStrands]="graphExpanded ? maxInOut : 24"
|
||||||
|
[network]="network"
|
||||||
|
[tooltip]="true">
|
||||||
|
</tx-bowtie-graph>
|
||||||
</div>
|
</div>
|
||||||
<div class="toggle-wrapper" *ngIf="maxInOut > 24">
|
<div class="toggle-wrapper" *ngIf="maxInOut > 24">
|
||||||
<button class="btn btn-sm btn-primary graph-toggle" (click)="expandGraph();" *ngIf="!graphExpanded; else collapseBtn"><span i18n="show-more">Show more</span></button>
|
<button class="btn btn-sm btn-primary graph-toggle" (click)="expandGraph();" *ngIf="!graphExpanded; else collapseBtn"><span i18n="show-more">Show more</span></button>
|
||||||
|
@ -50,7 +50,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
graphExpanded: boolean = false;
|
graphExpanded: boolean = false;
|
||||||
graphWidth: number = 1000;
|
graphWidth: number = 1000;
|
||||||
graphHeight: number = 360;
|
graphHeight: number = 360;
|
||||||
|
inOutLimit: number = 150;
|
||||||
maxInOut: number = 0;
|
maxInOut: number = 0;
|
||||||
|
|
||||||
tooltipPosition: { x: number, y: number };
|
tooltipPosition: { x: number, y: number };
|
||||||
|
|
||||||
@ViewChild('graphContainer')
|
@ViewChild('graphContainer')
|
||||||
@ -298,7 +300,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupGraph() {
|
setupGraph() {
|
||||||
this.maxInOut = Math.min(250, Math.max(this.tx?.vin?.length || 1, this.tx?.vout?.length + 1 || 1));
|
this.maxInOut = Math.min(this.inOutLimit, Math.max(this.tx?.vin?.length || 1, this.tx?.vout?.length + 1 || 1));
|
||||||
this.graphHeight = Math.min(360, this.maxInOut * 80);
|
this.graphHeight = Math.min(360, this.maxInOut * 80);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,8 +19,6 @@ interface Xput {
|
|||||||
confidential?: boolean;
|
confidential?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineLimit = 250;
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tx-bowtie-graph',
|
selector: 'tx-bowtie-graph',
|
||||||
templateUrl: './tx-bowtie-graph.component.html',
|
templateUrl: './tx-bowtie-graph.component.html',
|
||||||
@ -31,7 +29,8 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
@Input() network: string;
|
@Input() network: string;
|
||||||
@Input() width = 1200;
|
@Input() width = 1200;
|
||||||
@Input() height = 600;
|
@Input() height = 600;
|
||||||
@Input() combinedWeight = 100;
|
@Input() lineLimit = 250;
|
||||||
|
@Input() maxCombinedWeight = 100;
|
||||||
@Input() minWeight = 2; //
|
@Input() minWeight = 2; //
|
||||||
@Input() maxStrands = 24; // number of inputs/outputs to keep fully on-screen.
|
@Input() maxStrands = 24; // number of inputs/outputs to keep fully on-screen.
|
||||||
@Input() tooltip = false;
|
@Input() tooltip = false;
|
||||||
@ -42,6 +41,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
outputs: SvgLine[];
|
outputs: SvgLine[];
|
||||||
middle: SvgLine;
|
middle: SvgLine;
|
||||||
midWidth: number;
|
midWidth: number;
|
||||||
|
combinedWeight: number;
|
||||||
isLiquid: boolean = false;
|
isLiquid: boolean = false;
|
||||||
hoverLine: Xput | void = null;
|
hoverLine: Xput | void = null;
|
||||||
tooltipPosition = { x: 0, y: 0 };
|
tooltipPosition = { x: 0, y: 0 };
|
||||||
@ -62,20 +62,19 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
gradient: string[] = ['#105fb0', '#105fb0'];
|
gradient: string[] = ['#105fb0', '#105fb0'];
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet');
|
|
||||||
this.gradient = this.gradientColors[this.network];
|
|
||||||
this.midWidth = Math.min(50, Math.ceil(this.width / 20));
|
|
||||||
this.initGraph();
|
this.initGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet');
|
|
||||||
this.gradient = this.gradientColors[this.network];
|
|
||||||
this.midWidth = Math.min(50, Math.ceil(this.width / 20));
|
|
||||||
this.initGraph();
|
this.initGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
initGraph(): void {
|
initGraph(): void {
|
||||||
|
this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet');
|
||||||
|
this.gradient = this.gradientColors[this.network];
|
||||||
|
this.midWidth = Math.min(10, Math.ceil(this.width / 100));
|
||||||
|
this.combinedWeight = Math.min(this.maxCombinedWeight, Math.floor((this.width - (2 * this.midWidth)) / 6));
|
||||||
|
|
||||||
const totalValue = this.calcTotalValue(this.tx);
|
const totalValue = this.calcTotalValue(this.tx);
|
||||||
let voutWithFee = this.tx.vout.map(v => {
|
let voutWithFee = this.tx.vout.map(v => {
|
||||||
return {
|
return {
|
||||||
@ -103,19 +102,19 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
} as Xput;
|
} as Xput;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (truncatedInputs.length > lineLimit) {
|
if (truncatedInputs.length > this.lineLimit) {
|
||||||
const valueOfRest = truncatedInputs.slice(lineLimit).reduce((r, v) => {
|
const valueOfRest = truncatedInputs.slice(this.lineLimit).reduce((r, v) => {
|
||||||
return r + (v.value || 0);
|
return r + (v.value || 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
truncatedInputs = truncatedInputs.slice(0, lineLimit);
|
truncatedInputs = truncatedInputs.slice(0, this.lineLimit);
|
||||||
truncatedInputs.push({ type: 'input', value: valueOfRest, rest: this.tx.vin.length - lineLimit });
|
truncatedInputs.push({ type: 'input', value: valueOfRest, rest: this.tx.vin.length - this.lineLimit });
|
||||||
}
|
}
|
||||||
if (voutWithFee.length > lineLimit) {
|
if (voutWithFee.length > this.lineLimit) {
|
||||||
const valueOfRest = voutWithFee.slice(lineLimit).reduce((r, v) => {
|
const valueOfRest = voutWithFee.slice(this.lineLimit).reduce((r, v) => {
|
||||||
return r + (v.value || 0);
|
return r + (v.value || 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
voutWithFee = voutWithFee.slice(0, lineLimit);
|
voutWithFee = voutWithFee.slice(0, this.lineLimit);
|
||||||
voutWithFee.push({ type: 'output', value: valueOfRest, rest: outputCount - lineLimit });
|
voutWithFee.push({ type: 'output', value: valueOfRest, rest: outputCount - this.lineLimit });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.inputData = truncatedInputs;
|
this.inputData = truncatedInputs;
|
||||||
@ -126,7 +125,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
this.middle = {
|
this.middle = {
|
||||||
path: `M ${(this.width / 2) - this.midWidth} ${(this.height / 2) + 0.5} L ${(this.width / 2) + this.midWidth} ${(this.height / 2) + 0.5}`,
|
path: `M ${(this.width / 2) - this.midWidth} ${(this.height / 2) + 0.5} L ${(this.width / 2) + this.midWidth} ${(this.height / 2) + 0.5}`,
|
||||||
style: `stroke-width: ${this.combinedWeight + 0.5}; stroke: ${this.gradient[1]}`
|
style: `stroke-width: ${this.combinedWeight + 1}; stroke: ${this.gradient[1]}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +156,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
initLines(side: 'in' | 'out', xputs: Xput[], total: number, maxVisibleStrands: number): SvgLine[] {
|
initLines(side: 'in' | 'out', xputs: Xput[], total: number, maxVisibleStrands: number): SvgLine[] {
|
||||||
if (!total) {
|
if (!total) {
|
||||||
const weights = xputs.map((put): number => this.combinedWeight / xputs.length);
|
const weights = xputs.map((put) => this.combinedWeight / xputs.length);
|
||||||
return this.linesFromWeights(side, xputs, weights, maxVisibleStrands);
|
return this.linesFromWeights(side, xputs, weights, maxVisibleStrands);
|
||||||
} else {
|
} else {
|
||||||
let unknownCount = 0;
|
let unknownCount = 0;
|
||||||
@ -171,19 +170,26 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
});
|
});
|
||||||
const unknownShare = unknownTotal / unknownCount;
|
const unknownShare = unknownTotal / unknownCount;
|
||||||
// conceptual weights
|
// conceptual weights
|
||||||
const weights = xputs.map((put): number => this.combinedWeight * (put.value == null ? unknownShare : put.value as number) / total);
|
const weights = xputs.map((put) => this.combinedWeight * (put.value == null ? unknownShare : put.value as number) / total);
|
||||||
return this.linesFromWeights(side, xputs, weights, maxVisibleStrands);
|
return this.linesFromWeights(side, xputs, weights, maxVisibleStrands);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
linesFromWeights(side: 'in' | 'out', xputs: Xput[], weights: number[], maxVisibleStrands: number) {
|
linesFromWeights(side: 'in' | 'out', xputs: Xput[], weights: number[], maxVisibleStrands: number): SvgLine[] {
|
||||||
const lines = [];
|
const lineParams = weights.map((w) => {
|
||||||
// actual displayed line thicknesses
|
return {
|
||||||
const minWeights = weights.map((w) => Math.max(this.minWeight - 1, w) + 1);
|
weight: w,
|
||||||
|
thickness: Math.max(this.minWeight - 1, w) + 1,
|
||||||
|
offset: 0,
|
||||||
|
innerY: 0,
|
||||||
|
outerY: 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
const visibleStrands = Math.min(maxVisibleStrands, xputs.length);
|
const visibleStrands = Math.min(maxVisibleStrands, xputs.length);
|
||||||
const visibleWeight = minWeights.slice(0, visibleStrands).reduce((acc, v) => v + acc, 0);
|
const visibleWeight = lineParams.slice(0, visibleStrands).reduce((acc, v) => v.thickness + acc, 0);
|
||||||
const gaps = visibleStrands - 1;
|
const gaps = visibleStrands - 1;
|
||||||
|
|
||||||
|
// bounds of the middle segment
|
||||||
const innerTop = (this.height / 2) - (this.combinedWeight / 2);
|
const innerTop = (this.height / 2) - (this.combinedWeight / 2);
|
||||||
const innerBottom = innerTop + this.combinedWeight;
|
const innerBottom = innerTop + this.combinedWeight;
|
||||||
// tracks the visual bottom of the endpoints of the previous line
|
// tracks the visual bottom of the endpoints of the previous line
|
||||||
@ -192,39 +198,91 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
// gap between strands
|
// gap between strands
|
||||||
const spacing = (this.height - visibleWeight) / gaps;
|
const spacing = (this.height - visibleWeight) / gaps;
|
||||||
|
|
||||||
for (let i = 0; i < xputs.length; i++) {
|
// curve adjustments to prevent overlaps
|
||||||
const weight = weights[i];
|
let offset = 0;
|
||||||
const minWeight = minWeights[i];
|
let minOffset = 0;
|
||||||
|
let maxOffset = 0;
|
||||||
|
let lastWeight = 0;
|
||||||
|
let pad = 0;
|
||||||
|
lineParams.forEach((line, i) => {
|
||||||
// set the vertical position of the (center of the) outer side of the line
|
// set the vertical position of the (center of the) outer side of the line
|
||||||
let outer = lastOuter + (minWeight / 2);
|
line.outerY = lastOuter + (line.thickness / 2);
|
||||||
const inner = Math.min(innerBottom + (minWeight / 2), Math.max(innerTop + (minWeight / 2), lastInner + (weight / 2)));
|
line.innerY = Math.min(innerBottom + (line.thickness / 2), Math.max(innerTop + (line.thickness / 2), lastInner + (line.weight / 2)));
|
||||||
|
|
||||||
// special case to center single input/outputs
|
// special case to center single input/outputs
|
||||||
if (xputs.length === 1) {
|
if (xputs.length === 1) {
|
||||||
outer = (this.height / 2);
|
line.outerY = (this.height / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastOuter += minWeight + spacing;
|
lastOuter += line.thickness + spacing;
|
||||||
lastInner += weight;
|
lastInner += line.weight;
|
||||||
lines.push({
|
|
||||||
path: this.makePath(side, outer, inner, minWeight),
|
// calculate conservative lower bound of the amount of horizontal offset
|
||||||
style: this.makeStyle(minWeight, xputs[i].type),
|
// required to prevent this line overlapping its neighbor
|
||||||
|
|
||||||
|
if (this.tooltip || !xputs[i].rest) {
|
||||||
|
const w = (this.width - Math.max(lastWeight, line.weight)) / 2; // approximate horizontal width of the curved section of the line
|
||||||
|
const y1 = line.outerY;
|
||||||
|
const y2 = line.innerY;
|
||||||
|
const t = (lastWeight + line.weight) / 2; // distance between center of this line and center of previous line
|
||||||
|
|
||||||
|
// slope of the inflection point of the bezier curve
|
||||||
|
const dx = 0.75 * w;
|
||||||
|
const dy = 1.5 * (y2 - y1);
|
||||||
|
const a = Math.atan2(dy, dx);
|
||||||
|
|
||||||
|
// parallel curves should be separated by >=t at the inflection point to prevent overlap
|
||||||
|
// vertical offset is always = t, contributing tCos(a)
|
||||||
|
// horizontal offset h will contribute hSin(a)
|
||||||
|
// tCos(a) + hSin(a) >= t
|
||||||
|
// h >= t(1 - cos(a)) / sin(a)
|
||||||
|
if (Math.sin(a) !== 0) {
|
||||||
|
// (absolute value clamped to t for sanity)
|
||||||
|
offset += Math.max(Math.min(t * (1 - Math.cos(a)) / Math.sin(a), t), -t);
|
||||||
|
}
|
||||||
|
|
||||||
|
line.offset = offset;
|
||||||
|
minOffset = Math.min(minOffset, offset);
|
||||||
|
maxOffset = Math.max(maxOffset, offset);
|
||||||
|
pad = Math.max(pad, line.thickness / 2);
|
||||||
|
lastWeight = line.weight;
|
||||||
|
} else {
|
||||||
|
// skip the offsets for consolidated lines in unfurls, since these *should* overlap a little
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// normalize offsets
|
||||||
|
lineParams.forEach((line) => {
|
||||||
|
line.offset -= minOffset;
|
||||||
|
});
|
||||||
|
maxOffset -= minOffset;
|
||||||
|
|
||||||
|
return lineParams.map((line, i) => {
|
||||||
|
return {
|
||||||
|
path: this.makePath(side, line.outerY, line.innerY, line.thickness, line.offset, pad + maxOffset),
|
||||||
|
style: this.makeStyle(line.thickness, xputs[i].type),
|
||||||
class: xputs[i].type
|
class: xputs[i].type
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines;
|
makePath(side: 'in' | 'out', outer: number, inner: number, weight: number, offset: number, pad: number): string {
|
||||||
}
|
const start = (weight * 0.5);
|
||||||
|
const curveStart = Math.max(start + 1, pad - offset);
|
||||||
|
const end = this.width / 2 - (this.midWidth * 0.9) + 1;
|
||||||
|
const curveEnd = end - offset - 10;
|
||||||
|
const midpoint = (curveStart + curveEnd) / 2;
|
||||||
|
|
||||||
makePath(side: 'in' | 'out', outer: number, inner: number, weight: number): string {
|
|
||||||
const start = side === 'in' ? (weight * 0.5) : this.width - (weight * 0.5);
|
|
||||||
const center = this.width / 2 + (side === 'in' ? -(this.midWidth * 0.9) : (this.midWidth * 0.9) );
|
|
||||||
const midpoint = (start + center) / 2;
|
|
||||||
// correct for svg horizontal gradient bug
|
// correct for svg horizontal gradient bug
|
||||||
if (Math.round(outer) === Math.round(inner)) {
|
if (Math.round(outer) === Math.round(inner)) {
|
||||||
outer -= 1;
|
outer -= 1;
|
||||||
}
|
}
|
||||||
return `M ${start} ${outer} C ${midpoint} ${outer}, ${midpoint} ${inner}, ${center} ${inner}`;
|
|
||||||
|
if (side === 'in') {
|
||||||
|
return `M ${start} ${outer} L ${curveStart} ${outer} C ${midpoint} ${outer}, ${midpoint} ${inner}, ${curveEnd} ${inner} L ${end} ${inner}`;
|
||||||
|
} else { // mirrored in y-axis for the right hand side
|
||||||
|
return `M ${this.width - start} ${outer} L ${this.width - curveStart} ${outer} C ${this.width - midpoint} ${outer}, ${this.width - midpoint} ${inner}, ${this.width - curveEnd} ${inner} L ${this.width - end} ${inner}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
makeStyle(minWeight, type): string {
|
makeStyle(minWeight, type): string {
|
||||||
|
@ -11,37 +11,58 @@
|
|||||||
<div class="second-line"><app-amount [satoshis]="channel.capacity" digitsInfo="1.2-2"></app-amount></div>
|
<div class="second-line"><app-amount [satoshis]="channel.capacity" digitsInfo="1.2-2"></app-amount></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
|
||||||
|
|
||||||
<div class="col-md">
|
<div class="box">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-sent">Fee rate</td>
|
<td i18n="lightning.fee-rate">Fee rate</td>
|
||||||
<td>
|
<td>
|
||||||
{{ channel.fee_rate ?? '-' }} <span class="symbol">ppm ({{ channel.fee_rate / 10000 | number }}%)</span>
|
<span class="d-inline-block d-md-none">
|
||||||
|
{{ channel.fee_rate !== null ? (channel.fee_rate | amountShortener : 2 : undefined : true) : '-' }} <span class="symbol">ppm {{ channel.fee_rate !== null ? '(' + (channel.fee_rate / 10000 | amountShortener : 2 : undefined : true) + '%)' : '' }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="d-none d-md-inline-block">
|
||||||
|
{{ channel.fee_rate !== null ? (channel.fee_rate | number) : '-' }} <span class="symbol">ppm {{ channel.fee_rate !== null ? '(' + (channel.fee_rate / 10000 | number) + '%)' : '' }}</span>
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-sent">Base fee</td>
|
<td i18n="lightning.base-fee">Base fee</td>
|
||||||
<td>
|
<td class="base-fee">
|
||||||
<app-sats [valueOverride]="!channel.base_fee_mtokens ? '- ' : undefined" [satoshis]="channel.base_fee_mtokens / 1000" digitsInfo="1.0-2"></app-sats>
|
<span class="d-flex justify-content-between align-items-center">
|
||||||
|
<span>
|
||||||
|
<span *ngIf="channel.base_fee_mtokens !== null">
|
||||||
|
{{ channel.base_fee_mtokens | amountShortener : 0 }}
|
||||||
|
<span class="symbol">msats</span>
|
||||||
|
</span>
|
||||||
|
<span *ngIf="channel.base_fee_mtokens === null">
|
||||||
|
-
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span *ngIf="channel.base_fee_mtokens !== null" class="badge" [class]="channel.base_fee_mtokens === 0 ? 'badge-success' : 'badge-danger'"
|
||||||
|
i18n-ngbTooltip="lightning.zero-base-fee"
|
||||||
|
[ngbTooltip]="channel.base_fee_mtokens === 0 ? 'This channel supports zero base fee routing' :
|
||||||
|
'This channel does not support zero base fee routing'"
|
||||||
|
placement="bottom" i18n="lightning.zerobasefee">
|
||||||
|
{{ channel.base_fee_mtokens === 0 ? 'Zero base fee' : 'Non-zero base fee' }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-sent">Min HTLC</td>
|
<td i18n="lightning.min-htlc">Min HTLC</td>
|
||||||
<td>
|
<td>
|
||||||
<app-sats [valueOverride]="!channel.min_htlc_mtokens ? '- ' : undefined" [satoshis]="channel.min_htlc_mtokens / 1000"></app-sats>
|
<app-sats [valueOverride]="channel.min_htlc_mtokens === null ? '- ' : undefined" [satoshis]="channel.min_htlc_mtokens / 1000"></app-sats>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-sent">Max HTLC</td>
|
<td i18n="lightning.max-htlc">Max HTLC</td>
|
||||||
<td>
|
<td>
|
||||||
<app-sats [valueOverride]="!channel.max_htlc_mtokens ? '- ' : undefined" [satoshis]="channel.max_htlc_mtokens / 1000"></app-sats>
|
<app-sats [valueOverride]="channel.max_htlc_mtokens === null ? '- ' : undefined" [satoshis]="channel.max_htlc_mtokens / 1000"></app-sats>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-sent">Timelock delta</td>
|
<td i18n="lightning.timelock-detla">Timelock delta</td>
|
||||||
<td>
|
<td>
|
||||||
<ng-container *ngTemplateOutlet="blocksPlural; context: {$implicit: channel.cltv_delta ?? '-' }"></ng-container>
|
<ng-container *ngTemplateOutlet="blocksPlural; context: {$implicit: channel.cltv_delta ?? '-' }"></ng-container>
|
||||||
</td>
|
</td>
|
||||||
@ -49,6 +70,5 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
|
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
|
||||||
|
@ -22,3 +22,9 @@
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.base-fee {
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding-right: 0px;
|
||||||
|
}
|
||||||
|
}
|
@ -99,9 +99,7 @@
|
|||||||
<app-qrcode [size]="200" [data]="node.socketsObject[selectedSocketIndex].socket"></app-qrcode>
|
<app-qrcode [size]="200" [data]="node.socketsObject[selectedSocketIndex].socket"></app-qrcode>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-secondary ml-1" type="button" id="inputGroupFileAddon04">
|
<app-clipboard [button]="true" [text]="node.socketsObject[selectedSocketIndex].socket" [leftPadding]="false"></app-clipboard>
|
||||||
<app-clipboard [text]="node.socketsObject[selectedSocketIndex].socket" [leftPadding]="false"></app-clipboard>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="city text-right text-truncate d-none d-md-table-cell">
|
<td class="city text-right text-truncate d-none d-md-table-cell">
|
||||||
|
@ -85,9 +85,12 @@
|
|||||||
{{ node.as_organization }} [ASN {{node.as_number}}]
|
{{ node.as_organization }} [ASN {{node.as_number}}]
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="!node.as_number">
|
<td *ngIf="clearnetSocketCount === 0 && torSocketCount > 0">
|
||||||
<span class="badge badge-success" placement="bottom" i18n="tor">Exclusively on Tor</span>
|
<span class="badge badge-success" placement="bottom" i18n="tor">Exclusively on Tor</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td *ngIf="node.sockets.length === 0">
|
||||||
|
<span i18n="unknown">Unknown</span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -120,9 +123,7 @@
|
|||||||
<app-qrcode [size]="200" [data]="node.socketsObject[selectedSocketIndex].socket"></app-qrcode>
|
<app-qrcode [size]="200" [data]="node.socketsObject[selectedSocketIndex].socket"></app-qrcode>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-secondary ml-1" type="button" id="inputGroupFileAddon04">
|
<app-clipboard [button]="true" [text]="node.socketsObject[selectedSocketIndex].socket" [leftPadding]="false"></app-clipboard>
|
||||||
<app-clipboard [text]="node.socketsObject[selectedSocketIndex].socket" [leftPadding]="false"></app-clipboard>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!error">
|
<div *ngIf="!error">
|
||||||
@ -230,9 +231,7 @@
|
|||||||
<button class="btn btn-secondary ml-1" type="button" id="inputGroupFileAddon04" [disabled]="true">
|
<button class="btn btn-secondary ml-1" type="button" id="inputGroupFileAddon04" [disabled]="true">
|
||||||
<fa-icon [icon]="['fas', 'qrcode']" [fixedWidth]="true"></fa-icon>
|
<fa-icon [icon]="['fas', 'qrcode']" [fixedWidth]="true"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-secondary ml-1" type="button" id="inputGroupFileAddon04" [disabled]="true">
|
<app-clipboard [button]="true" [text]="''"></app-clipboard>
|
||||||
<app-clipboard [text]="''"></app-clipboard>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
@ -22,6 +22,8 @@ export class NodeComponent implements OnInit {
|
|||||||
error: Error;
|
error: Error;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
channelListLoading = false;
|
channelListLoading = false;
|
||||||
|
clearnetSocketCount = 0;
|
||||||
|
torSocketCount = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private lightningApiService: LightningApiService,
|
private lightningApiService: LightningApiService,
|
||||||
@ -47,10 +49,13 @@ export class NodeComponent implements OnInit {
|
|||||||
let label = '';
|
let label = '';
|
||||||
if (socket.match(/(?:[0-9]{1,3}\.){3}[0-9]{1,3}/)) {
|
if (socket.match(/(?:[0-9]{1,3}\.){3}[0-9]{1,3}/)) {
|
||||||
label = 'IPv4';
|
label = 'IPv4';
|
||||||
|
this.clearnetSocketCount++;
|
||||||
} else if (socket.indexOf('[') > -1) {
|
} else if (socket.indexOf('[') > -1) {
|
||||||
label = 'IPv6';
|
label = 'IPv6';
|
||||||
|
this.clearnetSocketCount++;
|
||||||
} else if (socket.indexOf('onion') > -1) {
|
} else if (socket.indexOf('onion') > -1) {
|
||||||
label = 'Tor';
|
label = 'Tor';
|
||||||
|
this.torSocketCount++;
|
||||||
}
|
}
|
||||||
socketsObject.push({
|
socketsObject.push({
|
||||||
label: label,
|
label: label,
|
||||||
|
@ -31,18 +31,26 @@ export class GeolocationComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.type === 'list-country') {
|
if (this.type === 'list-country') {
|
||||||
if (this.data.city) {
|
if (!this.data.city) {
|
||||||
|
this.formattedLocation = '-';
|
||||||
|
}
|
||||||
|
else if (this.data.city) {
|
||||||
this.formattedLocation += ' ' + city;
|
this.formattedLocation += ' ' + city;
|
||||||
if (this.data.subdivision) {
|
if (this.data.subdivision) {
|
||||||
this.formattedLocation += ', ' + subdivision;
|
this.formattedLocation += ', ' + subdivision;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.formattedLocation += '-';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.type === 'list-isp') {
|
if (this.type === 'list-isp') {
|
||||||
|
if (!this.data.country && !this.data.city) {
|
||||||
|
this.formattedLocation = '-';
|
||||||
|
} else {
|
||||||
|
if (this.data.country) {
|
||||||
this.formattedLocation = getFlagEmoji(this.data.iso);
|
this.formattedLocation = getFlagEmoji(this.data.iso);
|
||||||
|
} else {
|
||||||
|
this.formattedLocation = '';
|
||||||
|
}
|
||||||
if (this.data.city) {
|
if (this.data.city) {
|
||||||
this.formattedLocation += ' ' + city;
|
this.formattedLocation += ' ' + city;
|
||||||
if (this.data.subdivision) {
|
if (this.data.subdivision) {
|
||||||
@ -52,6 +60,7 @@ export class GeolocationComponent implements OnChanges {
|
|||||||
this.formattedLocation += ' ' + this.data.country;
|
this.formattedLocation += ' ' + this.data.country;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.type === 'node') {
|
if (this.type === 'node') {
|
||||||
const city = this.data.city ? this.data.city : '';
|
const city = this.data.city ? this.data.city : '';
|
||||||
|
@ -7,6 +7,7 @@ export class AmountShortenerPipe implements PipeTransform {
|
|||||||
transform(num: number, ...args: any[]): unknown {
|
transform(num: number, ...args: any[]): unknown {
|
||||||
const digits = args[0] ?? 1;
|
const digits = args[0] ?? 1;
|
||||||
const unit = args[1] || undefined;
|
const unit = args[1] || undefined;
|
||||||
|
const isMoney = args[2] || false;
|
||||||
|
|
||||||
if (num < 1000) {
|
if (num < 1000) {
|
||||||
return num.toFixed(digits);
|
return num.toFixed(digits);
|
||||||
@ -16,7 +17,7 @@ export class AmountShortenerPipe implements PipeTransform {
|
|||||||
{ value: 1, symbol: '' },
|
{ value: 1, symbol: '' },
|
||||||
{ value: 1e3, symbol: 'k' },
|
{ value: 1e3, symbol: 'k' },
|
||||||
{ value: 1e6, symbol: 'M' },
|
{ value: 1e6, symbol: 'M' },
|
||||||
{ value: 1e9, symbol: 'G' },
|
{ value: 1e9, symbol: isMoney ? 'B' : 'G' },
|
||||||
{ value: 1e12, symbol: 'T' },
|
{ value: 1e12, symbol: 'T' },
|
||||||
{ value: 1e15, symbol: 'P' },
|
{ value: 1e15, symbol: 'P' },
|
||||||
{ value: 1e18, symbol: 'E' }
|
{ value: 1e18, symbol: 'E' }
|
||||||
|
@ -10,7 +10,8 @@ $nav-tabs-link-active-bg: #11131f;
|
|||||||
$primary: #105fb0;
|
$primary: #105fb0;
|
||||||
$secondary: #2d3348;
|
$secondary: #2d3348;
|
||||||
$success: #1a9436;
|
$success: #1a9436;
|
||||||
$info: #1bd8f4;
|
$info: #5f4ab8;
|
||||||
|
$light: #744ad1;
|
||||||
|
|
||||||
$h5-font-size: 1.15rem !default;
|
$h5-font-size: 1.15rem !default;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user