mirror of
https://github.com/mempool/mempool.git
synced 2026-08-13 12:33:11 +02:00
added similarity support in chunked component
This commit is contained in:
parent
e9b90a86d6
commit
3a4f1252a9
4 changed files with 133 additions and 30 deletions
|
|
@ -93,7 +93,7 @@
|
|||
</ng-container>
|
||||
<ng-container *ngSwitchCase="vin.prevout && vin.prevout.scriptpubkey_type === 'p2pk'">
|
||||
<span>P2PK <a class="address p2pk-address" [routerLink]="['/address/' | relativeUrl, vin.prevout.scriptpubkey.slice(2, -2)]" title="{{ vin.prevout.scriptpubkey.slice(2, -2) }}">
|
||||
<app-chunked-address [address]="vin.prevout.scriptpubkey.slice(2, -2)" [lastChars]="8"></app-chunked-address>
|
||||
<app-chunked-address [address]="vin.prevout.scriptpubkey.slice(2, -2)"></app-chunked-address>
|
||||
</a>
|
||||
<app-clipboard [text]="vin.prevout.scriptpubkey.slice(2, -2)" class="ml-2" size="normal"></app-clipboard></span>
|
||||
</ng-container>
|
||||
|
|
@ -105,11 +105,12 @@
|
|||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template #defaultAddress>
|
||||
<app-address-text
|
||||
<app-chunked-address
|
||||
*ngIf="vin.prevout.scriptpubkey_address; else vinScriptPubkeyType"
|
||||
[address]="vin.prevout.scriptpubkey_address"
|
||||
[similarity]="similarityMatches.get(tx.txid)?.get(vin.prevout.scriptpubkey_address)"
|
||||
></app-address-text>
|
||||
[link]="['/address/' | relativeUrl, vin.prevout.scriptpubkey_address]"
|
||||
></app-chunked-address>
|
||||
<ng-template #vinScriptPubkeyType>
|
||||
{{ vin.prevout.scriptpubkey_type?.toUpperCase() }}
|
||||
</ng-template>
|
||||
|
|
@ -326,15 +327,16 @@
|
|||
'sigged': selectedSig && selectedSig.txIndex === i && sigHighlights.vout[vindex],
|
||||
}">
|
||||
<td class="address-cell">
|
||||
<app-address-text
|
||||
<app-chunked-address
|
||||
*ngIf="vout.scriptpubkey_address; else pubkey_type"
|
||||
[address]="vout.scriptpubkey_address"
|
||||
[similarity]="similarityMatches.get(tx.txid)?.get(vout.scriptpubkey_address)"
|
||||
></app-address-text>
|
||||
[link]="['/address/' | relativeUrl, vout.scriptpubkey_address]"
|
||||
></app-chunked-address>
|
||||
<ng-template #pubkey_type>
|
||||
<ng-container *ngIf="vout.scriptpubkey_type === 'p2pk'; else scriptpubkey_type">
|
||||
P2PK <a class="address p2pk-address" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey.slice(2, -2)]" title="{{ vout.scriptpubkey.slice(2, -2) }}">
|
||||
<app-chunked-address [address]="vout.scriptpubkey.slice(2, -2)" [lastChars]="8"></app-chunked-address>
|
||||
<app-chunked-address [address]="vout.scriptpubkey.slice(2, -2)"></app-chunked-address>
|
||||
</a>
|
||||
<app-clipboard [text]="vout.scriptpubkey.slice(2, -2)" class="ml-2" size="normal"></app-clipboard>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,54 @@
|
|||
<span class="truncate" [style.max-width]="maxWidth ? maxWidth + 'px' : null" [style.justify-content]="textAlign" [class.inline]="inline">
|
||||
|
||||
<ng-container *ngIf="link">
|
||||
<a [routerLink]="link" [queryParams]="queryParams" class="truncate-link" [target]="external ? '_blank' : '_self'" [class.disabled]="disabled">
|
||||
<ng-container *ngIf="rtl; then rtlTruncated; else ltrTruncated;"></ng-container>
|
||||
</a>
|
||||
</ng-container>
|
||||
@if (similarity) {
|
||||
<div class="address-poisoned">
|
||||
<ng-container *ngIf="link">
|
||||
<a style="display: contents;" [routerLink]="['/address/' | relativeUrl, address]" title="{{ address }}">
|
||||
<ng-container *ngTemplateOutlet="poisonedContent"></ng-container>
|
||||
</a>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!link">
|
||||
<ng-container *ngTemplateOutlet="poisonedContent"></ng-container>
|
||||
</ng-container>
|
||||
<span class="poison-alert" *ngIf="similarity" i18n-ngbTooltip="address-poisoning.warning-tooltip" ngbTooltip="This address is deceptively similar to another output. It may be part of an address poisoning attack.">
|
||||
<fa-icon [icon]="['fas', 'exclamation-triangle']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="address" [style.max-width]="maxWidth ? maxWidth + 'px' : null" [style.justify-content]="textAlign" [class.inline]="inline">
|
||||
<ng-container *ngIf="link">
|
||||
<a [routerLink]="link" [queryParams]="queryParams" class="address-link" [target]="external ? '_blank' : '_self'" [class.disabled]="disabled">
|
||||
<ng-container *ngIf="rtl; then rtlTruncated; else ltrTruncated;"></ng-container>
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!link">
|
||||
<ng-container *ngIf="rtl; then rtlTruncated; else ltrTruncated;"></ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-content></ng-content>
|
||||
</span>
|
||||
<ng-container *ngIf="!link">
|
||||
<ng-container *ngIf="rtl; then rtlTruncated; else ltrTruncated;"></ng-container>
|
||||
</ng-container>
|
||||
|
||||
<app-clipboard
|
||||
[text]="address" size="normal"
|
||||
class="d-none d-lg-block ml-lg-2">
|
||||
</app-clipboard>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
}
|
||||
|
||||
<ng-template #poisonedContent>
|
||||
<span class="prefix">
|
||||
<span *ngFor="let chunk of prefixChunks"
|
||||
[style.color]="chunk.color"
|
||||
[class.spacer]="chunk.hasMargin">{{ chunk.text }}</span>
|
||||
</span>
|
||||
<span class="infix" [ngStyle]="{'text-decoration-color': groupColors[similarity.group % (groupColors.length)]}">
|
||||
<span *ngFor="let chunk of infixChunks"
|
||||
[style.color]="chunk.color"
|
||||
[class.spacer]="chunk.hasMargin">{{ chunk.text }}</span>
|
||||
</span>
|
||||
<span class="postfix">
|
||||
<span *ngFor="let chunk of postfixChunks"
|
||||
[style.color]="chunk.color"
|
||||
[class.spacer]="chunk.hasMargin">{{ chunk.text }}</span>
|
||||
</span>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #ltrTruncated>
|
||||
<span class="first">
|
||||
|
|
@ -23,7 +60,7 @@
|
|||
<ng-template #simpleHead>{{ address.slice(0, -lastChars) }}</ng-template>
|
||||
</span>
|
||||
|
||||
<span class="last-four">
|
||||
<span class="last">
|
||||
<ng-container *ngIf="tailChunks.length; else simpleTail">
|
||||
<span *ngFor="let chunk of tailChunks"
|
||||
[style.color]="chunk.color"
|
||||
|
|
@ -42,7 +79,7 @@
|
|||
</ng-container>
|
||||
<ng-template #simpleTailRTL>{{ address.slice(-lastChars) }}</ng-template>
|
||||
</span>
|
||||
<span class="last-four">
|
||||
<span class="last">
|
||||
<ng-container *ngIf="headChunks.length; else simpleHeadRTL">
|
||||
<span *ngFor="let chunk of headChunks" [style.color]="chunk.color">{{ chunk.text }}</span>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,43 @@
|
|||
.truncate {
|
||||
.address-poisoned {
|
||||
text-overflow: unset;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
position: relative;
|
||||
font-family: monospace;
|
||||
|
||||
.truncate-link {
|
||||
&:hover {
|
||||
filter: brightness(0.85);
|
||||
}
|
||||
|
||||
.infix {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
|
||||
text-decoration: underline 2px;
|
||||
}
|
||||
|
||||
.prefix, .postfix {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
user-select: none;
|
||||
|
||||
text-decoration: underline var(--red) 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.address {
|
||||
text-overflow: unset;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
position: relative;
|
||||
font-family: monospace;
|
||||
|
||||
.address-link {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
|
|
@ -26,7 +58,7 @@
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
.last-four {
|
||||
.last {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
user-select: none;
|
||||
|
|
@ -53,6 +85,11 @@
|
|||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.poison-alert {
|
||||
margin-left: .5em;
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
@media (max-width: 567px) {
|
||||
.hidden-content {
|
||||
max-width: 150px !important;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, Input, ChangeDetectionStrategy, OnChanges, OnInit, OnDestroy, LOCALE_ID, Inject } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { AddressMatch } from '@app/shared/address-utils';
|
||||
|
||||
interface AddressChunk {
|
||||
text: string;
|
||||
|
|
@ -16,6 +16,7 @@ interface AddressChunk {
|
|||
})
|
||||
export class ChunkedAddressComponent implements OnChanges, OnInit, OnDestroy {
|
||||
@Input() address: string;
|
||||
@Input() similarity: { score: number, match: AddressMatch, group: number } | null;
|
||||
@Input() link: any = null;
|
||||
@Input() external: boolean = false;
|
||||
@Input() queryParams: any = undefined;
|
||||
|
|
@ -29,8 +30,21 @@ export class ChunkedAddressComponent implements OnChanges, OnInit, OnDestroy {
|
|||
headChunks: AddressChunk[] = [];
|
||||
tailChunks: AddressChunk[] = [];
|
||||
|
||||
prefixChunks: AddressChunk[] = [];
|
||||
infixChunks: AddressChunk[] = [];
|
||||
postfixChunks: AddressChunk[] = [];
|
||||
|
||||
private colors = ['var(--info)', 'var(--primary)'];
|
||||
|
||||
min = Math.min;
|
||||
|
||||
groupColors: string[] = [
|
||||
'var(--primary)',
|
||||
'var(--success)',
|
||||
'var(--info)',
|
||||
'white',
|
||||
];
|
||||
|
||||
constructor(
|
||||
@Inject(LOCALE_ID) private locale: string
|
||||
) {
|
||||
|
|
@ -52,12 +66,25 @@ export class ChunkedAddressComponent implements OnChanges, OnInit, OnDestroy {
|
|||
return
|
||||
};
|
||||
|
||||
const splitIndex = this.address.length - this.lastChars;
|
||||
const headText = this.address.slice(0, splitIndex);
|
||||
const tailText = this.address.slice(splitIndex);
|
||||
if (this.similarity) {
|
||||
const prefixLen = Math.min(this.similarity.match.prefix.length, 16) || 0;
|
||||
const postfixLen = Math.min(this.similarity.match.postfix.length, 16) || 0;
|
||||
|
||||
this.headChunks = this.chunkify(headText, 0);
|
||||
this.tailChunks = this.chunkify(tailText, splitIndex);
|
||||
const prefixText = this.address.slice(0, prefixLen);
|
||||
const infixText = this.address.slice(prefixLen, this.address.length - postfixLen);
|
||||
const postfixText = this.address.slice(this.address.length - postfixLen);
|
||||
|
||||
this.prefixChunks = this.chunkify(prefixText, 0);
|
||||
this.infixChunks = this.chunkify(infixText, prefixLen);
|
||||
this.postfixChunks = this.chunkify(postfixText, this.address.length - postfixLen);
|
||||
} else {
|
||||
const splitIndex = this.address.length - this.lastChars;
|
||||
const headText = this.address.slice(0, splitIndex);
|
||||
const tailText = this.address.slice(splitIndex);
|
||||
|
||||
this.headChunks = this.chunkify(headText, 0);
|
||||
this.tailChunks = this.chunkify(tailText, splitIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private chunkify(chunk: string, offset: number): AddressChunk[] {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue