mirror of
https://github.com/mempool/mempool.git
synced 2026-08-13 12:33:11 +02:00
Revert "feat: Address chunking by color & spaces"
This commit is contained in:
parent
bf61ad0ddf
commit
1739efe6f5
12 changed files with 88 additions and 282 deletions
|
|
@ -2,7 +2,7 @@
|
|||
<div class="title-address">
|
||||
<h1 i18n="shared.address">Address</h1>
|
||||
<div class="tx-link">
|
||||
<app-chunked-address [address]="addressString" [link]="['/address/' | relativeUrl, addressString]">
|
||||
<app-truncate [text]="addressString" [lastChars]="8" [link]="['/address/' | relativeUrl, addressString]">
|
||||
<span class="qrSpan" (mouseover)="showQR = true" (mouseout)="showQR = false" (pointerdown)="showQR = true">
|
||||
<fa-icon [icon]="['fas', 'qrcode']" [fixedWidth]="true" [style.font-size]="isMobile ? '18px' : '12px'"></fa-icon>
|
||||
<div class="qr-wrapper" [hidden]="!showQR">
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
</span>
|
||||
<app-clipboard [text]="addressString" [size]="isMobile ? 'large' : 'normal'"></app-clipboard>
|
||||
</app-chunked-address>
|
||||
</app-truncate>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||
private audioService: AudioService,
|
||||
private apiService: ApiService,
|
||||
private seoService: SeoService,
|
||||
private formBuilder: UntypedFormBuilder
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
|
|
|
|||
|
|
@ -93,9 +93,8 @@
|
|||
</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)"></app-chunked-address>
|
||||
</a>
|
||||
<app-clipboard [text]="vin.prevout.scriptpubkey.slice(2, -2)" class="ml-2" size="normal"></app-clipboard></span>
|
||||
<app-truncate [text]="vin.prevout.scriptpubkey.slice(2, -2)" [lastChars]="8"></app-truncate>
|
||||
</a></span>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<ng-template [ngIf]="!vin.prevout" [ngIfElse]="defaultAddress">
|
||||
|
|
@ -105,13 +104,11 @@
|
|||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template #defaultAddress>
|
||||
<app-chunked-address
|
||||
<app-address-text
|
||||
*ngIf="vin.prevout.scriptpubkey_address; else vinScriptPubkeyType"
|
||||
[address]="vin.prevout.scriptpubkey_address"
|
||||
[similarity]="similarityMatches.get(tx.txid)?.get(vin.prevout.scriptpubkey_address)"
|
||||
[link]="['/address/' | relativeUrl, vin.prevout.scriptpubkey_address]"
|
||||
[showClipboard]="true"
|
||||
></app-chunked-address>
|
||||
></app-address-text>
|
||||
<ng-template #vinScriptPubkeyType>
|
||||
{{ vin.prevout.scriptpubkey_type?.toUpperCase() }}
|
||||
</ng-template>
|
||||
|
|
@ -328,19 +325,16 @@
|
|||
'sigged': selectedSig && selectedSig.txIndex === i && sigHighlights.vout[vindex],
|
||||
}">
|
||||
<td class="address-cell">
|
||||
<app-chunked-address
|
||||
<app-address-text
|
||||
*ngIf="vout.scriptpubkey_address; else pubkey_type"
|
||||
[address]="vout.scriptpubkey_address"
|
||||
[similarity]="similarityMatches.get(tx.txid)?.get(vout.scriptpubkey_address)"
|
||||
[link]="['/address/' | relativeUrl, vout.scriptpubkey_address]"
|
||||
[showClipboard]="true"
|
||||
></app-chunked-address>
|
||||
></app-address-text>
|
||||
<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)"></app-chunked-address>
|
||||
<app-truncate [text]="vout.scriptpubkey.slice(2, -2)" [lastChars]="8"></app-truncate>
|
||||
</a>
|
||||
<app-clipboard [text]="vout.scriptpubkey.slice(2, -2)" class="ml-2" size="normal"></app-clipboard>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { SighashFlag } from '@app/shared/transaction.utils';
|
|||
templateUrl: './transactions-list.component.html',
|
||||
styleUrls: ['./transactions-list.component.scss'],
|
||||
standalone: false,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
|
||||
network = '';
|
||||
|
|
@ -91,7 +91,7 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
|
|||
private ref: ChangeDetectorRef,
|
||||
private priceService: PriceService,
|
||||
private storageService: StorageService,
|
||||
private route: ActivatedRoute
|
||||
private route: ActivatedRoute,
|
||||
) {
|
||||
this.signaturesMode = this.forceSignaturesMode || this.stateService.signaturesMode$.value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
@if (similarity) {
|
||||
<div class="address-text">
|
||||
<a class="address" style="display: contents;" [routerLink]="['/address/' | relativeUrl, address]" title="{{ address }}">
|
||||
<span class="prefix">{{ similarity.match.prefix.slice(0, 16) }}</span>
|
||||
<span class="infix" [ngStyle]="{'text-decoration-color': groupColors[similarity.group % (groupColors.length)]}">{{ address.slice(min(similarity.match.prefix.length, 16) || 0, -min(similarity.match.postfix.length, 16) || undefined) }}</span>
|
||||
<span class="postfix"> {{ similarity.match.postfix.slice(-16) }}</span>
|
||||
</a>
|
||||
<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 {
|
||||
<a class="address" [routerLink]="['/address/' | relativeUrl, address]" title="{{ address }}">
|
||||
<app-truncate [text]="address" [lastChars]="8"></app-truncate>
|
||||
</a>
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
.address-text {
|
||||
text-overflow: unset;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
position: relative;
|
||||
|
||||
font-family: monospace;
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.poison-alert {
|
||||
margin-left: .5em;
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { Component, Input } from '@angular/core';
|
||||
import { AddressMatch, AddressTypeInfo } from '@app/shared/address-utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-address-text',
|
||||
templateUrl: './address-text.component.html',
|
||||
styleUrls: ['./address-text.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class AddressTextComponent {
|
||||
@Input() address: string;
|
||||
@Input() info: AddressTypeInfo | null;
|
||||
@Input() similarity: { score: number, match: AddressMatch, group: number } | null;
|
||||
|
||||
min = Math.min;
|
||||
|
||||
groupColors: string[] = [
|
||||
'var(--primary)',
|
||||
'var(--success)',
|
||||
'var(--info)',
|
||||
'white',
|
||||
];
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
<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 *ngTemplateOutlet="truncated"></ng-container>
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!link">
|
||||
<ng-container *ngTemplateOutlet="truncated"></ng-container>
|
||||
</ng-container>
|
||||
|
||||
<app-clipboard
|
||||
*ngIf="!similarity && showClipboard"
|
||||
[text]="address" size="normal"
|
||||
class="d-none d-sm-block"
|
||||
></app-clipboard>
|
||||
|
||||
<span *ngIf="similarity" class="poison-alert" 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>
|
||||
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
|
||||
<ng-template #chunkTpl let-chunk>
|
||||
<ng-container *ngIf="similarity; else plain">
|
||||
<span class="prefix" *ngIf="chunk.prefixLength">{{ chunk.text.slice(0, chunk.prefixLength) }}</span><span class="infix" *ngIf="chunk.text.length - (chunk.prefixLength || 0) - (chunk.postfixLength || 0) > 0" [style.border-bottom-color]="matchColor">{{ chunk.text.slice(chunk.prefixLength || 0, chunk.text.length - (chunk.postfixLength || 0)) }}</span><span class="postfix" *ngIf="chunk.postfixLength">{{ chunk.text.slice(chunk.text.length - chunk.postfixLength) }}</span>
|
||||
</ng-container>
|
||||
<ng-template #plain>{{ chunk.text }}</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #truncated>
|
||||
<div class="address-content">
|
||||
<span class="first">
|
||||
<ng-container *ngIf="headChunks.length; else simpleHead">
|
||||
<span *ngFor="let chunk of headChunks" [class.partial]="chunk.text.length < chunkSize"><ng-container *ngTemplateOutlet="chunkTpl; context: { $implicit: chunk }"></ng-container></span>
|
||||
</ng-container>
|
||||
<ng-template #simpleHead>{{ address.slice(0, -lastChars) }}</ng-template>
|
||||
</span>
|
||||
<span class="last">
|
||||
<ng-container *ngIf="tailChunks.length; else simpleTail">
|
||||
<span *ngIf="(address.length - lastChars) % (chunkSize * 2) >= chunkSize" class="hidden"></span>
|
||||
<span *ngFor="let chunk of tailChunks"><ng-container *ngTemplateOutlet="chunkTpl; context: { $implicit: chunk }"></ng-container></span>
|
||||
</ng-container>
|
||||
<ng-template #simpleTail>{{ address.slice(-lastChars) }}</ng-template>
|
||||
</span>
|
||||
<div class="hidden-content">{{ address }}</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
.address {
|
||||
text-overflow: unset;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
position: relative;
|
||||
font-family: monospace;
|
||||
|
||||
.address-link, .address-content {
|
||||
direction: ltr;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.address-link:hover {
|
||||
filter: brightness(0.85);
|
||||
}
|
||||
|
||||
.first {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.last {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.first, .last {
|
||||
direction: ltr;
|
||||
> span {
|
||||
padding-inline-end: 2px;
|
||||
&:nth-child(even) {
|
||||
color: #18e2cf;
|
||||
}
|
||||
|
||||
&.partial {
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
&:has(> .prefix, > .infix, > .postfix) {
|
||||
padding-inline-end: 0;
|
||||
|
||||
> :last-child {
|
||||
padding-inline-end: 2px;
|
||||
}
|
||||
|
||||
&.partial > :last-child {
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.last span:last-child {
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
&.inline {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.hidden-content {
|
||||
color: transparent;
|
||||
position: absolute;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.infix {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
|
||||
border-bottom: 2px solid;
|
||||
}
|
||||
|
||||
.prefix, .postfix {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
user-select: none;
|
||||
|
||||
border-bottom: 2px solid var(--red);
|
||||
}
|
||||
}
|
||||
|
||||
.poison-alert {
|
||||
margin-left: .5em;
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
@media (max-width: 567px) {
|
||||
.hidden-content {
|
||||
max-width: 150px !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
import { Component, Input, ChangeDetectionStrategy, OnChanges } from '@angular/core';
|
||||
import { AddressMatch } from '@app/shared/address-utils';
|
||||
|
||||
interface AddressChunk {
|
||||
text: string;
|
||||
prefixLength?: number;
|
||||
postfixLength?: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-chunked-address',
|
||||
templateUrl: './chunked-address.component.html',
|
||||
styleUrls: ['./chunked-address.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false
|
||||
})
|
||||
export class ChunkedAddressComponent implements OnChanges {
|
||||
@Input() address: string;
|
||||
@Input() similarity: { score: number, match: AddressMatch, group: number } | null;
|
||||
@Input() link: any = null;
|
||||
@Input() external: boolean = false;
|
||||
@Input() queryParams: any = undefined;
|
||||
@Input() lastChars: number = 8;
|
||||
@Input() maxWidth: number = null;
|
||||
@Input() inline: boolean = false;
|
||||
@Input() textAlign: 'start' | 'end' = 'start';
|
||||
@Input() disabled: boolean = false;
|
||||
@Input() showClipboard: boolean = false;
|
||||
@Input() chunkSize = 4;
|
||||
|
||||
headChunks: AddressChunk[] = [];
|
||||
tailChunks: AddressChunk[] = [];
|
||||
|
||||
groupColors: string[] = [
|
||||
'var(--primary)',
|
||||
'var(--success)',
|
||||
'var(--info)',
|
||||
'white',
|
||||
];
|
||||
|
||||
matchColor: string = 'var(--primary)';
|
||||
|
||||
ngOnChanges(): void {
|
||||
if (this.similarity) {
|
||||
this.matchColor = this.groupColors[this.similarity.group % (this.groupColors.length)];
|
||||
}
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
private updateView(): void {
|
||||
if (!this.address) {
|
||||
return;
|
||||
}
|
||||
|
||||
const split = this.address.length - this.lastChars;
|
||||
const prefixLen = this.similarity?.match?.prefix?.length || 0;
|
||||
const postfixStart = this.address.length - (this.similarity?.match?.postfix?.length || 0);
|
||||
this.headChunks = [];
|
||||
this.tailChunks = [];
|
||||
|
||||
for (let i = 0; i < this.address.length; i += this.chunkSize) {
|
||||
const text = this.address.slice(i, i + this.chunkSize);
|
||||
const chunk = {
|
||||
text,
|
||||
prefixLength: prefixLen > i ? Math.min(text.length, prefixLen - i) : undefined,
|
||||
postfixLength: postfixStart < i + text.length ? Math.min(text.length, i + text.length - postfixStart) : undefined,
|
||||
};
|
||||
if (i + this.chunkSize <= split) {
|
||||
this.headChunks.push(chunk);
|
||||
} else if (i >= split) {
|
||||
this.tailChunks.push(chunk);
|
||||
} else {
|
||||
// split chunk across head and tail
|
||||
const [ headChunk, tailChunk ] = this.splitChunk(chunk, split - i);
|
||||
this.headChunks.push(headChunk);
|
||||
this.tailChunks.push(tailChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private splitChunk(chunk: AddressChunk, at: number): [AddressChunk, AddressChunk] {
|
||||
return [{
|
||||
text: chunk.text.slice(0, at),
|
||||
prefixLength: chunk.prefixLength ? Math.min(chunk.prefixLength, at) : undefined,
|
||||
postfixLength: chunk.postfixLength ? Math.max(0, at - chunk.postfixLength) : undefined,
|
||||
}, {
|
||||
text: chunk.text.slice(at),
|
||||
prefixLength: chunk.prefixLength ? Math.max(0, at - chunk.prefixLength) : undefined,
|
||||
postfixLength: chunk.postfixLength ? Math.min(chunk.postfixLength, chunk.text.length - at) : undefined,
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
|
@ -26,4 +26,4 @@ export class TruncateComponent {
|
|||
this.rtl = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ import { SatsComponent } from '@app/shared/components/sats/sats.component';
|
|||
import { BtcComponent } from '@app/shared/components/btc/btc.component';
|
||||
import { FeeRateComponent } from '@app/shared/components/fee-rate/fee-rate.component';
|
||||
import { AddressTypeComponent } from '@app/shared/components/address-type/address-type.component';
|
||||
import { AddressTextComponent } from '@app/shared/components/address-text/address-text.component';
|
||||
import { TruncateComponent } from '@app/shared/components/truncate/truncate.component';
|
||||
import { SearchResultsComponent } from '@components/search-form/search-results/search-results.component';
|
||||
import { TimestampComponent } from '@app/shared/components/timestamp/timestamp.component';
|
||||
|
|
@ -132,8 +133,6 @@ import { BitcoinInvoiceComponent } from '@components/bitcoin-invoice/bitcoin-inv
|
|||
import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/weight-directives/weight-directives';
|
||||
import { GithubLogin } from '@components/github-login.component/github-login.component';
|
||||
|
||||
import { ChunkedAddressComponent } from '@app/shared/components/chunked-address/chunked-address.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ClipboardComponent,
|
||||
|
|
@ -223,6 +222,7 @@ import { ChunkedAddressComponent } from '@app/shared/components/chunked-address/
|
|||
BtcComponent,
|
||||
FeeRateComponent,
|
||||
AddressTypeComponent,
|
||||
AddressTextComponent,
|
||||
TruncateComponent,
|
||||
SearchResultsComponent,
|
||||
TimestampComponent,
|
||||
|
|
@ -256,7 +256,6 @@ import { ChunkedAddressComponent } from '@app/shared/components/chunked-address/
|
|||
TwitterLogin,
|
||||
GithubLogin,
|
||||
BitcoinInvoiceComponent,
|
||||
ChunkedAddressComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
|
@ -376,6 +375,7 @@ import { ChunkedAddressComponent } from '@app/shared/components/chunked-address/
|
|||
BtcComponent,
|
||||
FeeRateComponent,
|
||||
AddressTypeComponent,
|
||||
AddressTextComponent,
|
||||
TruncateComponent,
|
||||
SearchResultsComponent,
|
||||
TimestampComponent,
|
||||
|
|
@ -407,8 +407,6 @@ import { ChunkedAddressComponent } from '@app/shared/components/chunked-address/
|
|||
|
||||
OnlyVsizeDirective,
|
||||
OnlyWeightDirective,
|
||||
|
||||
ChunkedAddressComponent
|
||||
]
|
||||
})
|
||||
export class SharedModule {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue