Merge branch 'master' into nymkappa/bugfix/diff-rounding
This commit is contained in:
@@ -1,4 +1,13 @@
|
||||
<span
|
||||
*ngIf="label"
|
||||
class="badge badge-pill badge-warning"
|
||||
>{{ label }}</span>
|
||||
<a *ngIf="channel; else default" [routerLink]="['/lightning/channel' | relativeUrl, channel.id]">
|
||||
<span
|
||||
*ngIf="label"
|
||||
class="badge badge-pill badge-warning"
|
||||
>{{ label }}</span>
|
||||
</a>
|
||||
|
||||
<ng-template #default>
|
||||
<span
|
||||
*ngIf="label"
|
||||
class="badge badge-pill badge-warning"
|
||||
>{{ label }}</span>
|
||||
</ng-template>
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, Input, OnChanges } from '@angular/core';
|
||||
import { Vin, Vout } from '../../interfaces/electrs.interface';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
|
||||
@@ -8,11 +8,12 @@ import { StateService } from 'src/app/services/state.service';
|
||||
styleUrls: ['./address-labels.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AddressLabelsComponent implements OnInit {
|
||||
export class AddressLabelsComponent implements OnChanges {
|
||||
network = '';
|
||||
|
||||
@Input() vin: Vin;
|
||||
@Input() vout: Vout;
|
||||
@Input() channel: any;
|
||||
|
||||
label?: string;
|
||||
|
||||
@@ -22,14 +23,21 @@ export class AddressLabelsComponent implements OnInit {
|
||||
this.network = stateService.network;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.vin) {
|
||||
ngOnChanges() {
|
||||
if (this.channel) {
|
||||
this.handleChannel();
|
||||
} else if (this.vin) {
|
||||
this.handleVin();
|
||||
} else if (this.vout) {
|
||||
this.handleVout();
|
||||
}
|
||||
}
|
||||
|
||||
handleChannel() {
|
||||
const type = this.vout ? 'open' : 'close';
|
||||
this.label = `Channel ${type}: ${this.channel.node_left.alias} <> ${this.channel.node_right.alias}`;
|
||||
}
|
||||
|
||||
handleVin() {
|
||||
if (this.vin.inner_witnessscript_asm) {
|
||||
if (this.vin.inner_witnessscript_asm.indexOf('OP_DEPTH OP_PUSHNUM_12 OP_EQUAL OP_IF OP_PUSHNUM_11') === 0) {
|
||||
|
||||
@@ -55,10 +55,7 @@
|
||||
<tr>
|
||||
<td i18n="block.timestamp">Timestamp</td>
|
||||
<td>
|
||||
‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}
|
||||
<div class="lg-inline">
|
||||
<i class="symbol">(<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since>)</i>
|
||||
</div>
|
||||
<app-timestamp [unixTime]="block.timestamp"></app-timestamp>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
3
frontend/src/app/components/change/change.component.html
Normal file
3
frontend/src/app/components/change/change.component.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<span [style]="change >= 0 ? 'color: #42B747' : 'color: #B74242'">
|
||||
{{ change >= 0 ? '+' : '' }}{{ change | amountShortener }}%
|
||||
</span>
|
||||
21
frontend/src/app/components/change/change.component.ts
Normal file
21
frontend/src/app/components/change/change.component.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-change',
|
||||
templateUrl: './change.component.html',
|
||||
styleUrls: ['./change.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ChangeComponent implements OnChanges {
|
||||
@Input() current: number;
|
||||
@Input() previous: number;
|
||||
|
||||
change: number;
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.change = (this.current - this.previous) / this.previous * 100;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<span #buttonWrapper [attr.data-tlite]="copiedMessage" style="position: relative;">
|
||||
<button #btn class="btn btn-sm btn-link pt-0" style="line-height: 0.9;" [attr.data-clipboard-text]="text">
|
||||
<img src="./resources/clippy.svg" width="13">
|
||||
<button #btn class="btn btn-sm btn-link pt-0" [style]="{'line-height': size === 'small' ? '0.2' : '0.8'}" [attr.data-clipboard-text]="text">
|
||||
<img src="./resources/clippy.svg" [width]="size === 'small' ? 10 : 13">
|
||||
</button>
|
||||
</span>
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
.btn-link {
|
||||
padding: 0.25rem 0 0.1rem 0.5rem;
|
||||
}
|
||||
|
||||
img {
|
||||
position: relative;
|
||||
left: -3px;
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import * as tlite from 'tlite';
|
||||
export class ClipboardComponent implements AfterViewInit {
|
||||
@ViewChild('btn') btn: ElementRef;
|
||||
@ViewChild('buttonWrapper') buttonWrapper: ElementRef;
|
||||
@Input() size: 'small' | 'normal' = 'normal';
|
||||
@Input() text: string;
|
||||
copiedMessage: string = $localize`:@@clipboard.copied-message:Copied!`;
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-pools" *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a class="nav-link" [routerLink]="['/mining' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'hammer']" [fixedWidth]="true" i18n-title="mining.mining-dashboard" title="Mining Dashboard"></fa-icon></a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-pools" *ngIf="stateService.env.LIGHTNING">
|
||||
<a class="nav-link" [routerLink]="['/lightning' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'bolt']" [fixedWidth]="true" i18n-title="master-page.lightning" title="Lightning Explorer"></fa-icon></a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active" id="btn-blocks" *ngIf="!stateService.env.MINING_DASHBOARD">
|
||||
<a class="nav-link" [routerLink]="['/blocks' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cubes']" [fixedWidth]="true" i18n-title="master-page.blocks" title="Blocks"></fa-icon></a>
|
||||
</li>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Component, Input, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
|
||||
import { Component, Input, AfterViewInit, ViewChild, ElementRef, ChangeDetectionStrategy } from '@angular/core';
|
||||
import * as QRCode from 'qrcode';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-qrcode',
|
||||
templateUrl: './qrcode.component.html',
|
||||
styleUrls: ['./qrcode.component.scss']
|
||||
styleUrls: ['./qrcode.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class QrcodeComponent implements AfterViewInit {
|
||||
@Input() data: string;
|
||||
@@ -19,7 +20,18 @@ export class QrcodeComponent implements AfterViewInit {
|
||||
private stateService: StateService,
|
||||
) { }
|
||||
|
||||
ngOnChanges() {
|
||||
if (!this.canvas || !this.canvas.nativeElement) {
|
||||
return;
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.stateService.isBrowser) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<form [formGroup]="searchForm" (submit)="searchForm.valid && search()" novalidate>
|
||||
<div class="d-flex">
|
||||
<div class="search-box-container mr-2">
|
||||
<input #instance="ngbTypeahead" [ngbTypeahead]="typeaheadSearchFn" [resultFormatter]="formatterFn" (selectItem)="itemSelected()" (focus)="focus$.next($any($event).target.value)" (click)="click$.next($any($event).target.value)" formControlName="searchText" type="text" class="form-control" i18n-placeholder="search-form.searchbar-placeholder" placeholder="TXID, block height, hash or address">
|
||||
<input (focus)="focus$.next($any($event).target.value)" (click)="click$.next($any($event).target.value)" formControlName="searchText" type="text" class="form-control" i18n-placeholder="search-form.searchbar-placeholder" placeholder="TXID, block height, hash or address">
|
||||
|
||||
<app-search-results #searchResults [results]="typeAhead$ | async" [searchTerm]="searchForm.get('searchText').value" (selectedResult)="selectedResult($event)"></app-search-results>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<button [disabled]="isSearching" type="submit" class="btn btn-block btn-primary"><fa-icon [icon]="['fas', 'search']" [fixedWidth]="true" i18n-title="search-form.search-title" title="Search"></fa-icon></button>
|
||||
|
||||
@@ -32,6 +32,7 @@ form {
|
||||
}
|
||||
|
||||
.search-box-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@media (min-width: 768px) {
|
||||
min-width: 400px;
|
||||
@@ -48,4 +49,4 @@ form {
|
||||
.btn {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,40 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output, ViewChild } from '@angular/core';
|
||||
import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output, ViewChild, HostListener } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { AssetsService } from 'src/app/services/assets.service';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
import { Observable, of, Subject, merge } from 'rxjs';
|
||||
import { Observable, of, Subject, merge, zip } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged, switchMap, filter, catchError, map } from 'rxjs/operators';
|
||||
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
|
||||
import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
|
||||
import { ShortenStringPipe } from 'src/app/shared/pipes/shorten-string-pipe/shorten-string.pipe';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
import { SearchResultsComponent } from './search-results/search-results.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-search-form',
|
||||
templateUrl: './search-form.component.html',
|
||||
styleUrls: ['./search-form.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SearchFormComponent implements OnInit {
|
||||
network = '';
|
||||
assets: object = {};
|
||||
isSearching = false;
|
||||
typeaheadSearchFn: ((text: Observable<string>) => Observable<readonly any[]>);
|
||||
|
||||
typeAhead$: Observable<any>;
|
||||
searchForm: FormGroup;
|
||||
isMobile = (window.innerWidth <= 767.98);
|
||||
@Output() searchTriggered = new EventEmitter();
|
||||
|
||||
regexAddress = /^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100})$/;
|
||||
regexBlockhash = /^[0]{8}[a-fA-F0-9]{56}$/;
|
||||
regexTransaction = /^([a-fA-F0-9]{64}):?(\d+)?$/;
|
||||
regexBlockheight = /^[0-9]+$/;
|
||||
|
||||
@ViewChild('instance', {static: true}) instance: NgbTypeahead;
|
||||
focus$ = new Subject<string>();
|
||||
click$ = new Subject<string>();
|
||||
|
||||
formatterFn = (address: string) => this.shortenStringPipe.transform(address, this.isMobile ? 33 : 40);
|
||||
@Output() searchTriggered = new EventEmitter();
|
||||
@ViewChild('searchResults') searchResults: SearchResultsComponent;
|
||||
@HostListener('keydown', ['$event']) keydown($event) {
|
||||
this.handleKeyDown($event);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
@@ -43,12 +42,11 @@ export class SearchFormComponent implements OnInit {
|
||||
private assetsService: AssetsService,
|
||||
private stateService: StateService,
|
||||
private electrsApiService: ElectrsApiService,
|
||||
private apiService: ApiService,
|
||||
private relativeUrlPipe: RelativeUrlPipe,
|
||||
private shortenStringPipe: ShortenStringPipe,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.typeaheadSearchFn = this.typeaheadSearch;
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
|
||||
this.searchForm = this.formBuilder.group({
|
||||
@@ -61,45 +59,74 @@ export class SearchFormComponent implements OnInit {
|
||||
this.assets = assets;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
typeaheadSearch = (text$: Observable<string>) => {
|
||||
const debouncedText$ = text$.pipe(
|
||||
map((text) => {
|
||||
if (this.network === 'bisq' && text.match(/^(b)[^c]/i)) {
|
||||
return text.substr(1);
|
||||
}
|
||||
return text;
|
||||
}),
|
||||
debounceTime(200),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
const clicksWithClosedPopup$ = this.click$.pipe(filter(() => !this.instance.isPopupOpen()));
|
||||
const inputFocus$ = this.focus$;
|
||||
|
||||
return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$)
|
||||
this.typeAhead$ = this.searchForm.get('searchText').valueChanges
|
||||
.pipe(
|
||||
map((text) => {
|
||||
if (this.network === 'bisq' && text.match(/^(b)[^c]/i)) {
|
||||
return text.substr(1);
|
||||
}
|
||||
return text.trim();
|
||||
}),
|
||||
debounceTime(250),
|
||||
distinctUntilChanged(),
|
||||
switchMap((text) => {
|
||||
if (!text.length) {
|
||||
return of([]);
|
||||
return of([
|
||||
[],
|
||||
{
|
||||
nodes: [],
|
||||
channels: [],
|
||||
}
|
||||
]);
|
||||
}
|
||||
return this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([])));
|
||||
if (!this.stateService.env.LIGHTNING) {
|
||||
return zip(
|
||||
this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([]))),
|
||||
[{ nodes: [], channels: [] }]
|
||||
);
|
||||
}
|
||||
return zip(
|
||||
this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([]))),
|
||||
this.apiService.lightningSearch$(text).pipe(catchError(() => of({
|
||||
nodes: [],
|
||||
channels: [],
|
||||
}))),
|
||||
);
|
||||
}),
|
||||
map((result: string[]) => {
|
||||
map((result: any[]) => {
|
||||
if (this.network === 'bisq') {
|
||||
return result.map((address: string) => 'B' + address);
|
||||
return result[0].map((address: string) => 'B' + address);
|
||||
}
|
||||
return result;
|
||||
return {
|
||||
addresses: result[0],
|
||||
nodes: result[1].nodes,
|
||||
channels: result[1].channels,
|
||||
totalResults: result[0].length + result[1].nodes.length + result[1].channels.length,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
handleKeyDown($event) {
|
||||
this.searchResults.handleKeyDown($event);
|
||||
}
|
||||
|
||||
itemSelected() {
|
||||
setTimeout(() => this.search());
|
||||
}
|
||||
|
||||
search() {
|
||||
const searchText = this.searchForm.value.searchText.trim();
|
||||
selectedResult(result: any) {
|
||||
if (typeof result === 'string') {
|
||||
this.search(result);
|
||||
} else if (result.alias) {
|
||||
this.navigate('/lightning/node/', result.public_key);
|
||||
} else if (result.short_id) {
|
||||
this.navigate('/lightning/channel/', result.id);
|
||||
}
|
||||
}
|
||||
|
||||
search(result?: string) {
|
||||
const searchText = result || this.searchForm.value.searchText.trim();
|
||||
if (searchText) {
|
||||
this.isSearching = true;
|
||||
if (this.regexAddress.test(searchText)) {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<div class="dropdown-menu show" *ngIf="results" [hidden]="!results.addresses.length && !results.nodes.length && !results.channels.length">
|
||||
<ng-template [ngIf]="results.addresses.length">
|
||||
<div class="card-title" *ngIf="stateService.env.LIGHTNING">Bitcoin Addresses</div>
|
||||
<ng-template ngFor [ngForOf]="results.addresses" let-address let-i="index">
|
||||
<button (click)="clickItem(i)" [class.active]="i === activeIdx" type="button" role="option" class="dropdown-item">
|
||||
<ngb-highlight [result]="address | shortenString : isMobile ? 25 : 36" [term]="searchTerm"></ngb-highlight>
|
||||
</button>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="results.nodes.length">
|
||||
<div class="card-title">Lightning Nodes</div>
|
||||
<ng-template ngFor [ngForOf]="results.nodes" let-node let-i="index">
|
||||
<button (click)="clickItem(results.addresses.length + i)" [class.active]="results.addresses.length + i === activeIdx" [routerLink]="['/lightning/node' | relativeUrl, node.public_key]" type="button" role="option" class="dropdown-item">
|
||||
<ngb-highlight [result]="node.alias" [term]="searchTerm"></ngb-highlight> <span class="symbol">{{ node.public_key | shortenString : 10 }}</span>
|
||||
</button>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="results.channels.length">
|
||||
<div class="card-title">Lightning Channels</div>
|
||||
<ng-template ngFor [ngForOf]="results.channels" let-channel let-i="index">
|
||||
<button (click)="clickItem(results.addresses.length + results.nodes.length + i)" [class.active]="results.addresses.length + results.nodes.length + i === activeIdx" type="button" role="option" class="dropdown-item">
|
||||
<ngb-highlight [result]="channel.short_id" [term]="searchTerm"></ngb-highlight> <span class="symbol">{{ channel.id }}</span>
|
||||
</button>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
font-size: 10px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 1rem;
|
||||
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
top: 42px;
|
||||
left: 0px;
|
||||
box-shadow: 0.125rem 0.125rem 0.25rem rgba(0,0,0,0.075);
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-search-results',
|
||||
templateUrl: './search-results.component.html',
|
||||
styleUrls: ['./search-results.component.scss'],
|
||||
})
|
||||
export class SearchResultsComponent implements OnChanges {
|
||||
@Input() results: any = {};
|
||||
@Input() searchTerm = '';
|
||||
@Output() selectedResult = new EventEmitter();
|
||||
|
||||
isMobile = (window.innerWidth <= 767.98);
|
||||
resultsFlattened = [];
|
||||
activeIdx = 0;
|
||||
focusFirst = true;
|
||||
|
||||
constructor(public stateService: StateService) { }
|
||||
|
||||
ngOnChanges() {
|
||||
this.activeIdx = 0;
|
||||
if (this.results) {
|
||||
this.resultsFlattened = [...this.results.addresses, ...this.results.nodes, ...this.results.channels];
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
event.preventDefault();
|
||||
this.next();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
event.preventDefault();
|
||||
this.prev();
|
||||
break;
|
||||
case 'Enter':
|
||||
event.preventDefault();
|
||||
if (this.resultsFlattened[this.activeIdx]) {
|
||||
this.selectedResult.emit(this.resultsFlattened[this.activeIdx]);
|
||||
} else {
|
||||
this.selectedResult.emit(this.searchTerm);
|
||||
}
|
||||
this.results = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
clickItem(id: number) {
|
||||
this.selectedResult.emit(this.resultsFlattened[id]);
|
||||
this.results = null;
|
||||
}
|
||||
|
||||
next() {
|
||||
if (this.activeIdx === this.resultsFlattened.length - 1) {
|
||||
this.activeIdx = this.focusFirst ? (this.activeIdx + 1) % this.resultsFlattened.length : -1;
|
||||
} else {
|
||||
this.activeIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
prev() {
|
||||
if (this.activeIdx < 0) {
|
||||
this.activeIdx = this.resultsFlattened.length - 1;
|
||||
} else if (this.activeIdx === 0) {
|
||||
this.activeIdx = this.focusFirst ? this.resultsFlattened.length - 1 : -1;
|
||||
} else {
|
||||
this.activeIdx--;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export class TimeSinceComponent implements OnInit, OnChanges, OnDestroy {
|
||||
intervals = {};
|
||||
|
||||
@Input() time: number;
|
||||
@Input() dateString: number;
|
||||
@Input() fastRender = false;
|
||||
|
||||
constructor(
|
||||
@@ -52,7 +53,13 @@ export class TimeSinceComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
calculate() {
|
||||
const seconds = Math.floor((+new Date() - +new Date(this.time * 1000)) / 1000);
|
||||
let date: Date;
|
||||
if (this.dateString) {
|
||||
date = new Date(this.dateString)
|
||||
} else {
|
||||
date = new Date(this.time * 1000);
|
||||
}
|
||||
const seconds = Math.floor((+new Date() - +date) / 1000);
|
||||
if (seconds < 60) {
|
||||
return $localize`:@@date-base.just-now:Just now`;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
{{ vin.prevout.scriptpubkey_type?.toUpperCase() }}
|
||||
</ng-template>
|
||||
<div>
|
||||
<app-address-labels [vin]="vin"></app-address-labels>
|
||||
<app-address-labels [vin]="vin" [channel]="channels && channels.inputs[i] || null"></app-address-labels>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
@@ -172,7 +172,7 @@
|
||||
</span>
|
||||
</a>
|
||||
<div>
|
||||
<app-address-labels [vout]="vout"></app-address-labels>
|
||||
<app-address-labels [vout]="vout" [channel]="channels && channels.outputs[i] && channels.outputs[i].transaction_vout === vindex ? channels.outputs[i] : null"></app-address-labels>
|
||||
</div>
|
||||
<ng-template #scriptpubkey_type>
|
||||
<ng-template [ngIf]="vout.pegout" [ngIfElse]="defaultscriptpubkey_type">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Observable, forkJoin, ReplaySubject, BehaviorSubject, merge, Subscription } from 'rxjs';
|
||||
import { Observable, ReplaySubject, BehaviorSubject, merge, Subscription } from 'rxjs';
|
||||
import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.interface';
|
||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { AssetsService } from 'src/app/services/assets.service';
|
||||
import { map, tap, switchMap } from 'rxjs/operators';
|
||||
import { filter, map, tap, switchMap } from 'rxjs/operators';
|
||||
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
|
||||
@@ -32,9 +32,11 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
latestBlock$: Observable<BlockExtended>;
|
||||
outspendsSubscription: Subscription;
|
||||
refreshOutspends$: ReplaySubject<string[]> = new ReplaySubject();
|
||||
refreshChannels$: ReplaySubject<string[]> = new ReplaySubject();
|
||||
showDetails$ = new BehaviorSubject<boolean>(false);
|
||||
outspends: Outspend[][] = [];
|
||||
assetsMinimal: any;
|
||||
channels: { inputs: any[], outputs: any[] };
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
@@ -73,7 +75,16 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
};
|
||||
}
|
||||
}),
|
||||
)
|
||||
),
|
||||
this.refreshChannels$
|
||||
.pipe(
|
||||
filter(() => this.stateService.env.LIGHTNING),
|
||||
switchMap((txIds) => this.apiService.getChannelByTxIds$(txIds)),
|
||||
map((channels) => {
|
||||
this.channels = channels;
|
||||
}),
|
||||
)
|
||||
,
|
||||
).subscribe(() => this.ref.markForCheck());
|
||||
}
|
||||
|
||||
@@ -114,8 +125,9 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
tx['addressValue'] = addressIn - addressOut;
|
||||
}
|
||||
});
|
||||
|
||||
this.refreshOutspends$.next(this.transactions.map((tx) => tx.txid));
|
||||
const txIds = this.transactions.map((tx) => tx.txid);
|
||||
this.refreshOutspends$.next(txIds);
|
||||
this.refreshChannels$.next(txIds);
|
||||
}
|
||||
|
||||
onScroll() {
|
||||
|
||||
Reference in New Issue
Block a user