Merge branch 'master' into nymkappa/bugfix/node-tor-badge
This commit is contained in:
		
						commit
						bf08498b72
					
				| @ -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;"> | ||||
|     <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> | ||||
|     </button> | ||||
|   </span> | ||||
| </ng-template> | ||||
|  | ||||
| @ -11,6 +11,8 @@ import * as tlite from 'tlite'; | ||||
| export class ClipboardComponent implements AfterViewInit { | ||||
|   @ViewChild('btn') btn: ElementRef; | ||||
|   @ViewChild('buttonWrapper') buttonWrapper: ElementRef; | ||||
|   @Input() button = false; | ||||
|   @Input() class = 'btn btn-secondary ml-1'; | ||||
|   @Input() size: 'small' | 'normal' = 'normal'; | ||||
|   @Input() text: string; | ||||
|   @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 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="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"/> | ||||
|       <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 offset="1" stop-color="#13EFD8"/> | ||||
|         </linearGradient> | ||||
|  | ||||
| @ -191,12 +191,20 @@ | ||||
|     <br> | ||||
| 
 | ||||
|     <div class="title"> | ||||
|       <h2 i18n="transaction.diagram|Transaction diagram">Diagram</h2> | ||||
|       <h2 i18n="transaction.flow|Transaction flow">Flow</h2> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="box"> | ||||
|       <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 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> | ||||
|  | ||||
| @ -50,7 +50,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|   graphExpanded: boolean = false; | ||||
|   graphWidth: number = 1000; | ||||
|   graphHeight: number = 360; | ||||
|   inOutLimit: number = 150; | ||||
|   maxInOut: number = 0; | ||||
| 
 | ||||
|   tooltipPosition: { x: number, y: number }; | ||||
| 
 | ||||
|   @ViewChild('graphContainer') | ||||
| @ -298,7 +300,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|   } | ||||
| 
 | ||||
|   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); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -19,8 +19,6 @@ interface Xput { | ||||
|   confidential?: boolean; | ||||
| } | ||||
| 
 | ||||
| const lineLimit = 250; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'tx-bowtie-graph', | ||||
|   templateUrl: './tx-bowtie-graph.component.html', | ||||
| @ -31,7 +29,8 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { | ||||
|   @Input() network: string; | ||||
|   @Input() width = 1200; | ||||
|   @Input() height = 600; | ||||
|   @Input() combinedWeight = 100; | ||||
|   @Input() lineLimit = 250; | ||||
|   @Input() maxCombinedWeight = 100; | ||||
|   @Input() minWeight = 2; //
 | ||||
|   @Input() maxStrands = 24; // number of inputs/outputs to keep fully on-screen.
 | ||||
|   @Input() tooltip = false; | ||||
| @ -42,6 +41,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { | ||||
|   outputs: SvgLine[]; | ||||
|   middle: SvgLine; | ||||
|   midWidth: number; | ||||
|   combinedWeight: number; | ||||
|   isLiquid: boolean = false; | ||||
|   hoverLine: Xput | void = null; | ||||
|   tooltipPosition = { x: 0, y: 0 }; | ||||
| @ -62,20 +62,19 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { | ||||
|   gradient: string[] = ['#105fb0', '#105fb0']; | ||||
| 
 | ||||
|   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(); | ||||
|   } | ||||
| 
 | ||||
|   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(); | ||||
|   } | ||||
| 
 | ||||
|   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); | ||||
|     let voutWithFee = this.tx.vout.map(v => { | ||||
|       return { | ||||
| @ -103,19 +102,19 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { | ||||
|       } as Xput; | ||||
|     }); | ||||
| 
 | ||||
|     if (truncatedInputs.length > lineLimit) { | ||||
|       const valueOfRest = truncatedInputs.slice(lineLimit).reduce((r, v) => { | ||||
|     if (truncatedInputs.length > this.lineLimit) { | ||||
|       const valueOfRest = truncatedInputs.slice(this.lineLimit).reduce((r, v) => { | ||||
|         return r + (v.value || 0); | ||||
|       }, 0); | ||||
|       truncatedInputs = truncatedInputs.slice(0, lineLimit); | ||||
|       truncatedInputs.push({ type: 'input', value: valueOfRest, rest: this.tx.vin.length - lineLimit }); | ||||
|       truncatedInputs = truncatedInputs.slice(0, this.lineLimit); | ||||
|       truncatedInputs.push({ type: 'input', value: valueOfRest, rest: this.tx.vin.length - this.lineLimit }); | ||||
|     } | ||||
|     if (voutWithFee.length > lineLimit) { | ||||
|       const valueOfRest = voutWithFee.slice(lineLimit).reduce((r, v) => { | ||||
|     if (voutWithFee.length > this.lineLimit) { | ||||
|       const valueOfRest = voutWithFee.slice(this.lineLimit).reduce((r, v) => { | ||||
|         return r + (v.value || 0); | ||||
|       }, 0); | ||||
|       voutWithFee = voutWithFee.slice(0, lineLimit); | ||||
|       voutWithFee.push({ type: 'output', value: valueOfRest, rest: outputCount - lineLimit }); | ||||
|       voutWithFee = voutWithFee.slice(0, this.lineLimit); | ||||
|       voutWithFee.push({ type: 'output', value: valueOfRest, rest: outputCount - this.lineLimit }); | ||||
|     } | ||||
| 
 | ||||
|     this.inputData = truncatedInputs; | ||||
| @ -126,7 +125,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { | ||||
| 
 | ||||
|     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}`, | ||||
|       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[] { | ||||
|     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); | ||||
|     } else { | ||||
|       let unknownCount = 0; | ||||
| @ -171,19 +170,26 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { | ||||
|       }); | ||||
|       const unknownShare = unknownTotal / unknownCount; | ||||
|       // 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); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   linesFromWeights(side: 'in' | 'out', xputs: Xput[], weights: number[], maxVisibleStrands: number) { | ||||
|     const lines = []; | ||||
|     // actual displayed line thicknesses
 | ||||
|     const minWeights = weights.map((w) => Math.max(this.minWeight - 1, w) + 1); | ||||
|   linesFromWeights(side: 'in' | 'out', xputs: Xput[], weights: number[], maxVisibleStrands: number): SvgLine[] { | ||||
|     const lineParams = weights.map((w) => { | ||||
|       return { | ||||
|         weight: w, | ||||
|         thickness: Math.max(this.minWeight - 1, w) + 1, | ||||
|         offset: 0, | ||||
|         innerY: 0, | ||||
|         outerY: 0, | ||||
|       }; | ||||
|     }); | ||||
|     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; | ||||
| 
 | ||||
|     // bounds of the middle segment
 | ||||
|     const innerTop = (this.height / 2) - (this.combinedWeight / 2); | ||||
|     const innerBottom = innerTop + this.combinedWeight; | ||||
|     // tracks the visual bottom of the endpoints of the previous line
 | ||||
| @ -192,39 +198,91 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { | ||||
|     // gap between strands
 | ||||
|     const spacing = (this.height - visibleWeight) / gaps; | ||||
| 
 | ||||
|     for (let i = 0; i < xputs.length; i++) { | ||||
|       const weight = weights[i]; | ||||
|       const minWeight = minWeights[i]; | ||||
|     // curve adjustments to prevent overlaps
 | ||||
|     let offset = 0; | ||||
|     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
 | ||||
|       let outer = lastOuter + (minWeight / 2); | ||||
|       const inner = Math.min(innerBottom + (minWeight / 2), Math.max(innerTop + (minWeight / 2), lastInner + (weight / 2))); | ||||
|       line.outerY = lastOuter + (line.thickness / 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
 | ||||
|       if (xputs.length === 1) { | ||||
|         outer = (this.height / 2); | ||||
|         line.outerY = (this.height / 2); | ||||
|       } | ||||
| 
 | ||||
|       lastOuter += minWeight + spacing; | ||||
|       lastInner += weight; | ||||
|       lines.push({ | ||||
|         path: this.makePath(side, outer, inner, minWeight), | ||||
|         style: this.makeStyle(minWeight, xputs[i].type), | ||||
|       lastOuter += line.thickness + spacing; | ||||
|       lastInner += line.weight; | ||||
| 
 | ||||
|       // calculate conservative lower bound of the amount of horizontal offset
 | ||||
|       // 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 | ||||
|       }; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|     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
 | ||||
|     if (Math.round(outer) === Math.round(inner)) { | ||||
|       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 { | ||||
|  | ||||
| @ -99,9 +99,7 @@ | ||||
|                   <app-qrcode [size]="200" [data]="node.socketsObject[selectedSocketIndex].socket"></app-qrcode> | ||||
|                 </div> | ||||
|               </button> | ||||
|               <button class="btn btn-secondary ml-1" type="button" id="inputGroupFileAddon04"> | ||||
|                 <app-clipboard [text]="node.socketsObject[selectedSocketIndex].socket" [leftPadding]="false"></app-clipboard> | ||||
|               </button> | ||||
|               <app-clipboard [button]="true" [text]="node.socketsObject[selectedSocketIndex].socket" [leftPadding]="false"></app-clipboard> | ||||
|             </div> | ||||
|           </td> | ||||
|           <td class="city text-right text-truncate d-none d-md-table-cell"> | ||||
|  | ||||
| @ -123,9 +123,7 @@ | ||||
|         <app-qrcode [size]="200" [data]="node.socketsObject[selectedSocketIndex].socket"></app-qrcode> | ||||
|       </div> | ||||
|     </button> | ||||
|     <button class="btn btn-secondary ml-1" type="button" id="inputGroupFileAddon04"> | ||||
|       <app-clipboard [text]="node.socketsObject[selectedSocketIndex].socket" [leftPadding]="false"></app-clipboard> | ||||
|     </button> | ||||
|     <app-clipboard [button]="true" [text]="node.socketsObject[selectedSocketIndex].socket" [leftPadding]="false"></app-clipboard> | ||||
|   </div> | ||||
| 
 | ||||
|   <div *ngIf="!error"> | ||||
| @ -233,9 +231,7 @@ | ||||
|       <button class="btn btn-secondary ml-1" type="button" id="inputGroupFileAddon04" [disabled]="true"> | ||||
|         <fa-icon [icon]="['fas', 'qrcode']" [fixedWidth]="true"></fa-icon> | ||||
|       </button> | ||||
|       <button class="btn btn-secondary ml-1" type="button" id="inputGroupFileAddon04" [disabled]="true"> | ||||
|         <app-clipboard [text]="''"></app-clipboard> | ||||
|       </button> | ||||
|       <app-clipboard [button]="true" [text]="''"></app-clipboard> | ||||
|     </div> | ||||
| 
 | ||||
|     <br> | ||||
|  | ||||
| @ -10,7 +10,8 @@ $nav-tabs-link-active-bg: #11131f; | ||||
| $primary: #105fb0; | ||||
| $secondary: #2d3348; | ||||
| $success: #1a9436; | ||||
| $info: #1bd8f4; | ||||
| $info: #5f4ab8; | ||||
| $light: #744ad1; | ||||
| 
 | ||||
| $h5-font-size: 1.15rem !default; | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user