Merge branch 'master' into nymkappa/clip-label-overflow

This commit is contained in:
nymkappa
2023-04-22 20:10:27 +02:00
committed by GitHub
81 changed files with 5058 additions and 2508 deletions

View File

@@ -204,9 +204,9 @@
<img class="image" src="/resources/profile/raspiblitz.svg" />
<span>RaspiBlitz</span>
</a>
<a href="https://github.com/mynodebtc/mynode" target="_blank" title="MyNode">
<img class="image" src="/resources/profile/mynodebtc.jpg" />
<span>MyNode</span>
<a href="https://github.com/mynodebtc/mynode" target="_blank" title="myNode">
<img class="image" src="/resources/profile/mynodebtc.png" />
<span>myNode</span>
</a>
<a href="https://github.com/RoninDojo/RoninDojo" target="_blank" title="RoninDojo">
<img class="image" src="/resources/profile/ronindojo.png" />
@@ -253,7 +253,7 @@
<span>Sparrow</span>
</a>
<a href="https://github.com/ACINQ/phoenix" target="_blank" title="Phoenix Wallet by ACINQ">
<img class="image" src="/resources/profile/phoenix.jpg" />
<img class="image not-rounded" src="/resources/profile/phoenix.svg" />
<span>Phoenix</span>
</a>
<a href="https://github.com/lnbits/lnbits-legend" target="_blank" title="LNbits">

View File

@@ -23,7 +23,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
@Input() unavailable: boolean = false;
@Input() auditHighlighting: boolean = false;
@Input() blockConversion: Price;
@Output() txClickEvent = new EventEmitter<TransactionStripped>();
@Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>();
@Output() txHoverEvent = new EventEmitter<string>();
@Output() readyEvent = new EventEmitter();
@@ -326,7 +326,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
if (event.target === this.canvas.nativeElement && event.pointerType === 'touch') {
this.setPreviewTx(event.offsetX, event.offsetY, true);
} else if (event.target === this.canvas.nativeElement) {
this.onTxClick(event.offsetX, event.offsetY);
const keyMod = event.shiftKey || event.ctrlKey || event.metaKey;
const middleClick = event.which === 2 || event.button === 1;
this.onTxClick(event.offsetX, event.offsetY, keyMod || middleClick);
}
}
@@ -409,12 +411,12 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
}
}
onTxClick(cssX: number, cssY: number) {
onTxClick(cssX: number, cssY: number, keyModifier: boolean = false) {
const x = cssX * window.devicePixelRatio;
const y = cssY * window.devicePixelRatio;
const selected = this.scene.getTxAt({ x, y });
if (selected && selected.txid) {
this.txClickEvent.emit(selected);
this.txClickEvent.emit({ tx: selected, keyModifier });
}
}

View File

@@ -612,9 +612,13 @@ export class BlockComponent implements OnInit, OnDestroy {
});
}
onTxClick(event: TransactionStripped): void {
const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.txid}`);
this.router.navigate([url]);
onTxClick(event: { tx: TransactionStripped, keyModifier: boolean }): void {
const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.tx.txid}`);
if (!event.keyModifier) {
this.router.navigate([url]);
} else {
window.open(url, '_blank');
}
}
onTxHover(txid: string): void {

View File

@@ -90,6 +90,8 @@
</nav>
</header>
<app-testnet-alert *ngIf="network.val === 'liquidtestnet'"></app-testnet-alert>
<br />
<router-outlet></router-outlet>

View File

@@ -62,6 +62,8 @@
</nav>
</header>
<app-testnet-alert *ngIf="network.val === 'testnet' || network.val === 'signet'"></app-testnet-alert>
<br />
<router-outlet></router-outlet>

View File

@@ -192,4 +192,4 @@ nav {
margin: 33px 0px 0px -19px;
font-size: 7px;
}
}
}

View File

@@ -1,4 +1,4 @@
import { Component, Inject, OnInit } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { Env, StateService } from '../../services/state.service';
import { Observable, merge, of } from 'rxjs';
import { LanguageService } from '../../services/language.service';

View File

@@ -107,8 +107,12 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
this.isLoading$.next(false);
}
onTxClick(event: TransactionStripped): void {
const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.txid}`);
this.router.navigate([url]);
onTxClick(event: { tx: TransactionStripped, keyModifier: boolean }): void {
const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.tx.txid}`);
if (!event.keyModifier) {
this.router.navigate([url]);
} else {
window.open(url, '_blank');
}
}
}

View File

@@ -2,7 +2,7 @@
<div class="mempool-blocks-container" [class.time-ltr]="timeLtr" *ngIf="(difficultyAdjustments$ | async) as da;">
<div class="flashing">
<ng-template ngFor let-projectedBlock [ngForOf]="mempoolBlocks$ | async" let-i="index" [ngForTrackBy]="trackByFn">
<div @blockEntryTrigger [@.disabled]="!animateEntry" [attr.data-cy]="'mempool-block-' + i" class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]" [class.blink-bg]="projectedBlock.blink">
<div @blockEntryTrigger [@.disabled]="i > 0 || !animateEntry" [attr.data-cy]="'mempool-block-' + i" class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]" [class.blink-bg]="projectedBlock.blink">
<a draggable="false" [routerLink]="['/mempool-block/' | relativeUrl, i]"
class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}">&nbsp;</a>
<div class="block-body">

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, HostListener } from '@angular/core';
import { Subscription, Observable, fromEvent, merge, of, combineLatest } from 'rxjs';
import { MempoolBlock } from '../../interfaces/websocket.interface';
import { StateService } from '../../services/state.service';
@@ -222,8 +222,13 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
clearTimeout(this.resetTransitionTimeout);
}
@HostListener('window:resize', ['$event'])
onResize(): void {
this.animateEntry = false;
}
trackByFn(index: number, block: MempoolBlock) {
return (block.isStack) ? 'stack' : block.index;
return (block.isStack) ? `stack-${block.index}` : block.index;
}
reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {

View File

@@ -137,9 +137,11 @@ export class StartComponent implements OnInit, OnDestroy {
}
onMouseDown(event: MouseEvent) {
this.mouseDragStartX = event.clientX;
this.resetMomentum(event.clientX);
this.blockchainScrollLeftInit = this.blockchainContainer.nativeElement.scrollLeft;
if (!(event.which > 1 || event.button > 0)) {
this.mouseDragStartX = event.clientX;
this.resetMomentum(event.clientX);
this.blockchainScrollLeftInit = this.blockchainContainer.nativeElement.scrollLeft;
}
}
onPointerDown(event: PointerEvent) {
if (this.isiOS) {

View File

@@ -8,10 +8,7 @@
</div>
<div *ngIf="network !== 'liquid' && network !== 'liquidtestnet'" class="features">
<app-tx-features [tx]="tx"></app-tx-features>
<span *ngIf="cpfpInfo && (cpfpInfo.bestDescendant || cpfpInfo.descendants.length)" class="badge badge-primary mr-1">
CPFP
</span>
<span *ngIf="cpfpInfo && !cpfpInfo.bestDescendant && !cpfpInfo.descendants.length && cpfpInfo.ancestors.length" class="badge badge-info mr-1">
<span *ngIf="cpfpInfo && (cpfpInfo?.bestDescendant || cpfpInfo?.descendants?.length || cpfpInfo?.ancestors?.length)" class="badge badge-primary ml-1 mr-1">
CPFP
</span>
</div>

View File

@@ -1,3 +1,5 @@
<div infiniteScroll [alwaysCallback]="true" [infiniteScrollDistance]="2" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="onScroll()">
<ng-container *ngFor="let tx of transactions; let i = index; trackBy: trackByFn">
<div *ngIf="!transactionPage" class="header-bg box tx-page-container">
<a class="tx-link" [routerLink]="['/tx/' | relativeUrl, tx.txid]">
@@ -11,7 +13,7 @@
</div>
</div>
<div class="header-bg box" infiniteScroll [alwaysCallback]="true" [infiniteScrollDistance]="2" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="onScroll()" [attr.data-cy]="'tx-' + i">
<div class="header-bg box" [attr.data-cy]="'tx-' + i">
<div *ngIf="errorUnblinded" class="error-unblinded">{{ errorUnblinded }}</div>
<div class="row">
@@ -321,6 +323,8 @@
</ng-container>
</div>
<ng-template #assetBox let-item>
{{ item.value / pow(10, assetsMinimal[item.asset][3]) | number: '1.' + assetsMinimal[item.asset][3] + '-' + assetsMinimal[item.asset][3] }} {{ assetsMinimal[item.asset][1] }}
<br />

View File

@@ -182,14 +182,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
}
onScroll(): void {
const scrollHeight = document.body.scrollHeight;
const scrollTop = document.documentElement.scrollTop;
if (scrollHeight > 0) {
const percentageScrolled = scrollTop * 100 / scrollHeight;
if (percentageScrolled > 50) {
this.loadMore.emit();
}
}
this.loadMore.emit();
}
haveBlindedOutputValues(tx: Transaction): boolean {

View File

@@ -29,7 +29,7 @@
<ng-template #pegout>
<ng-container *ngIf="line.pegout; else normal">
<p *ngIf="!isConnector">Peg Out</p>
<p *ngIf="line.value != null"><app-amount [satoshis]="line.value"></app-amount></p>
<p *ngIf="line.displayValue != null"><app-amount [satoshis]="line.displayValue"></app-amount></p>
<p class="address">
<app-truncate [text]="line.pegout"></app-truncate>
</p>
@@ -55,18 +55,18 @@
<p *ngSwitchCase="'output'"><span i18n="transaction.input">Input</span>&nbsp; #{{ line.vin + 1 }}</p>
</ng-container>
</ng-container>
<p *ngIf="line.value == null && line.confidential" i18n="shared.confidential">Confidential</p>
<p *ngIf="line.value != null">
<p *ngIf="line.displayValue == null && line.confidential" i18n="shared.confidential">Confidential</p>
<p *ngIf="line.displayValue != null">
<ng-template [ngIf]="line.asset && line.asset !== nativeAssetId" [ngIfElse]="defaultOutput">
<div *ngIf="assetsMinimal && assetsMinimal[line.asset] else assetNotFound">
<ng-container *ngTemplateOutlet="assetBox; context:{ $implicit: line }"></ng-container>
</div>
<ng-template #assetNotFound>
{{ line.value }} <span class="symbol">{{ line.asset | slice : 0 : 7 }}</span>
{{ line.displayValue }} <span class="symbol">{{ line.asset | slice : 0 : 7 }}</span>
</ng-template>
</ng-template>
<ng-template #defaultOutput>
<app-amount [blockConversion]="blockConversion" [satoshis]="line.value"></app-amount>
<app-amount [blockConversion]="blockConversion" [satoshis]="line.displayValue"></app-amount>
</ng-template>
</p>
<p *ngIf="line.type !== 'fee' && line.address" class="address">
@@ -76,5 +76,5 @@
</div>
<ng-template #assetBox let-item>
{{ item.value / pow(10, assetsMinimal[item.asset][3]) | number: '1.' + assetsMinimal[item.asset][3] + '-' + assetsMinimal[item.asset][3] }} <span class="symbol">{{ assetsMinimal[item.asset][1] }}</span>
{{ item.displayValue / pow(10, assetsMinimal[item.asset][3]) | number: '1.' + assetsMinimal[item.asset][3] + '-' + assetsMinimal[item.asset][3] }} <span class="symbol">{{ assetsMinimal[item.asset][1] }}</span>
</ng-template>

View File

@@ -7,6 +7,7 @@ import { environment } from '../../../environments/environment';
interface Xput {
type: 'input' | 'output' | 'fee';
value?: number;
displayValue?: number;
index?: number;
txid?: string;
vin?: number;

View File

@@ -1,12 +1,13 @@
import { Component, OnInit, Input, OnChanges, HostListener, Inject, LOCALE_ID } from '@angular/core';
import { StateService } from '../../services/state.service';
import { Outspend, Transaction } from '../../interfaces/electrs.interface';
import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.interface';
import { Router } from '@angular/router';
import { ReplaySubject, merge, Subscription, of } from 'rxjs';
import { tap, switchMap } from 'rxjs/operators';
import { ApiService } from '../../services/api.service';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { AssetsService } from '../../services/assets.service';
import { environment } from '../../../environments/environment';
interface SvgLine {
path: string;
@@ -20,6 +21,7 @@ interface SvgLine {
interface Xput {
type: 'input' | 'output' | 'fee';
value?: number;
displayValue?: number;
index?: number;
txid?: string;
vin?: number;
@@ -74,6 +76,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
zeroValueThickness = 20;
hasLine: boolean;
assetsMinimal: any;
nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
outspendsSubscription: Subscription;
refreshOutspends$: ReplaySubject<string> = new ReplaySubject();
@@ -167,7 +170,8 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
let voutWithFee = this.tx.vout.map((v, i) => {
return {
type: v.scriptpubkey_type === 'fee' ? 'fee' : 'output',
value: v?.value,
value: this.getOutputValue(v),
displayValue: v?.value,
address: v?.scriptpubkey_address || v?.scriptpubkey_type?.toUpperCase(),
index: i,
pegout: v?.pegout?.scriptpubkey_address,
@@ -185,7 +189,8 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
let truncatedInputs = this.tx.vin.map((v, i) => {
return {
type: 'input',
value: v?.prevout?.value || (v?.is_coinbase && !totalValue ? 0 : undefined),
value: (v?.is_coinbase && !totalValue ? 0 : this.getInputValue(v)),
displayValue: v?.prevout?.value,
txid: v.txid,
vout: v.vout,
address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(),
@@ -229,14 +234,14 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
}
calcTotalValue(tx: Transaction): number {
const totalOutput = this.tx.vout.reduce((acc, v) => (v.value == null ? 0 : v.value) + acc, 0);
let totalOutput = this.tx.vout.reduce((acc, v) => (this.getOutputValue(v) || 0) + acc, 0);
// simple sum of outputs + fee for bitcoin
if (!this.isLiquid) {
return this.tx.fee ? totalOutput + this.tx.fee : totalOutput;
} else {
const totalInput = this.tx.vin.reduce((acc, v) => (v?.prevout?.value == null ? 0 : v.prevout.value) + acc, 0);
const confidentialInputCount = this.tx.vin.reduce((acc, v) => acc + (v?.prevout?.value == null ? 1 : 0), 0);
const confidentialOutputCount = this.tx.vout.reduce((acc, v) => acc + (v.value == null ? 1 : 0), 0);
const totalInput = this.tx.vin.reduce((acc, v) => (this.getInputValue(v) || 0) + acc, 0);
const confidentialInputCount = this.tx.vin.reduce((acc, v) => acc + (this.isUnknownInputValue(v) ? 1 : 0), 0);
const confidentialOutputCount = this.tx.vout.reduce((acc, v) => acc + (this.isUnknownOutputValue(v) ? 1 : 0), 0);
// if there are unknowns on both sides, the total is indeterminate, so we'll just fudge it
if (confidentialInputCount && confidentialOutputCount) {
@@ -456,6 +461,34 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
}
}
getOutputValue(v: Vout): number | void {
if (!v) {
return null;
} else if (this.isLiquid && v.asset !== this.nativeAssetId) {
return null;
} else {
return v.value;
}
}
getInputValue(v: Vin): number | void {
if (!v?.prevout) {
return null;
} else if (this.isLiquid && v.prevout.asset !== this.nativeAssetId) {
return null;
} else {
return v.prevout.value;
}
}
isUnknownInputValue(v: Vin): boolean {
return v?.prevout?.value == null || this.isLiquid && v?.prevout?.asset !== this.nativeAssetId;
}
isUnknownOutputValue(v: Vout): boolean {
return v?.value == null || this.isLiquid && v?.asset !== this.nativeAssetId;
}
@HostListener('pointermove', ['$event'])
onPointerMove(event) {
if (this.dir === 'rtl') {