Merge branch 'master' into hunicus/msop-r
This commit is contained in:
@@ -5,12 +5,15 @@
|
||||
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="!confirmations && height != null">
|
||||
<button type="button" class="btn btn-sm btn-success {{buttonClass}}" i18n="transaction.confirmed|Transaction confirmed state">Confirmed</button>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="!hideUnconfirmed && !confirmations && replaced">
|
||||
<button type="button" class="btn btn-sm btn-warning {{buttonClass}}" i18n="transaction.replaced|Transaction replaced state">Replaced</button>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="!hideUnconfirmed && !confirmations && !replaced && removed">
|
||||
<button type="button" class="btn btn-sm btn-warning {{buttonClass}}" i18n="transaction.audit.removed|Transaction removed state">Removed</button>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="!hideUnconfirmed && !confirmations && !replaced && !removed">
|
||||
<ng-template [ngIf]="!hideUnconfirmed && chainTip != null && !confirmations && !replaced && !removed">
|
||||
<button type="button" class="btn btn-sm btn-danger {{buttonClass}}" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,4 @@
|
||||
<ng-container *ngIf="rateUnits$ | async as units">
|
||||
<ng-container *ngIf="units !== 'wu'">{{ fee / (weight / 4) | feeRounding:rounding }} <span *ngIf="showUnit" [class]="unitClass" [style]="unitStyle">sat/vB</span></ng-container>
|
||||
<ng-container *ngIf="units === 'wu'">{{ fee / weight | feeRounding:rounding }} <span *ngIf="showUnit" [class]="unitClass" [style]="unitStyle">sat/WU</span></ng-container>
|
||||
</ng-container>
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { StateService } from '../../../services/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-fee-rate',
|
||||
templateUrl: './fee-rate.component.html',
|
||||
styleUrls: ['./fee-rate.component.scss']
|
||||
})
|
||||
export class FeeRateComponent implements OnInit {
|
||||
@Input() fee: number;
|
||||
@Input() weight: number = 4;
|
||||
@Input() rounding: string = null;
|
||||
@Input() showUnit: boolean = true;
|
||||
@Input() unitClass: string = 'symbol';
|
||||
@Input() unitStyle: any;
|
||||
|
||||
rateUnits$: Observable<string>;
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.rateUnits$ = this.stateService.rateUnits$;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@
|
||||
<div class="selector">
|
||||
<app-fiat-selector></app-fiat-selector>
|
||||
</div>
|
||||
<div class="selector">
|
||||
<app-rate-unit-selector></app-rate-unit-selector>
|
||||
</div>
|
||||
<ng-template #temporaryHidden>
|
||||
<a *ngIf="officialMempoolSpace" class="cta btn btn-purple sponsor" [routerLink]="['/signup' | relativeUrl]">Support the Project</a>
|
||||
<p *ngIf="officialMempoolSpace && env.BASE_MODULE === 'mempool'" class="cta-secondary"><a [routerLink]="['/signin' | relativeUrl]" i18n="shared.broadcast-transaction|Broadcast Transaction">Sign In</a></p>
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
<span *ngIf="seconds !== undefined">
|
||||
‎{{ seconds * 1000 | date: customFormat ?? 'yyyy-MM-dd HH:mm' }}
|
||||
<div class="lg-inline" *ngIf="!hideTimeSince">
|
||||
<i class="symbol">(<app-time kind="since" [time]="seconds" [fastRender]="true"></app-time>)</i>
|
||||
<i class="symbol">(<app-time kind="since" [time]="seconds" [fastRender]="true" [precision]="precision" [minUnit]="minUnit"></app-time>)</i>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
@@ -11,6 +11,8 @@ export class TimestampComponent implements OnChanges {
|
||||
@Input() dateString: string;
|
||||
@Input() customFormat: string;
|
||||
@Input() hideTimeSince: boolean = false;
|
||||
@Input() precision: number = 0;
|
||||
@Input() minUnit: 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second' = 'second';
|
||||
|
||||
seconds: number | undefined = undefined;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<span class="truncate" [style.max-width]="maxWidth ? maxWidth + 'px' : null">
|
||||
<span class="truncate" [style.max-width]="maxWidth ? maxWidth + 'px' : null" [style.justify-content]="textAlign" [class.inline]="inline">
|
||||
<ng-container *ngIf="link">
|
||||
<a [routerLink]="link" class="truncate-link">
|
||||
<ng-container *ngIf="rtl; then rtlTruncated; else ltrTruncated;"></ng-container>
|
||||
|
||||
@@ -23,4 +23,8 @@
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
&.inline {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ export class TruncateComponent {
|
||||
@Input() link: any = null;
|
||||
@Input() lastChars: number = 4;
|
||||
@Input() maxWidth: number = null;
|
||||
@Input() inline: boolean = false;
|
||||
@Input() textAlign: 'start' | 'end' = 'start';
|
||||
rtl: boolean;
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Directive, OnDestroy, TemplateRef, ViewContainerRef } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { StateService } from '../../../services/state.service';
|
||||
|
||||
function createRateUnitDirective(checkFn: (rateUnit: string) => boolean): any {
|
||||
@Directive()
|
||||
class RateUnitDirective implements OnDestroy {
|
||||
private subscription: Subscription;
|
||||
private enabled: boolean;
|
||||
private hasView: boolean = false;
|
||||
|
||||
constructor(
|
||||
private templateRef: TemplateRef<any>,
|
||||
private viewContainer: ViewContainerRef,
|
||||
private stateService: StateService
|
||||
) {
|
||||
this.subscription = this.stateService.rateUnits$.subscribe(rateUnit => {
|
||||
this.enabled = checkFn(rateUnit);
|
||||
this.updateView();
|
||||
});
|
||||
}
|
||||
|
||||
updateView(): void {
|
||||
if (this.enabled && !this.hasView) {
|
||||
this.viewContainer.createEmbeddedView(this.templateRef);
|
||||
this.hasView = true;
|
||||
} else if (!this.enabled && this.hasView) {
|
||||
this.viewContainer.clear();
|
||||
this.hasView = false;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
return RateUnitDirective;
|
||||
}
|
||||
|
||||
@Directive({ selector: '[only-vsize]' })
|
||||
export class OnlyVsizeDirective extends createRateUnitDirective(rateUnit => rateUnit !== 'wu') {}
|
||||
|
||||
@Directive({ selector: '[only-weight]' })
|
||||
export class OnlyWeightDirective extends createRateUnitDirective(rateUnit => rateUnit === 'wu') {}
|
||||
28
frontend/src/app/shared/pipes/bitcoinsatoshis.pipe.ts
Normal file
28
frontend/src/app/shared/pipes/bitcoinsatoshis.pipe.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||
|
||||
@Pipe({
|
||||
name: 'bitcoinsatoshis'
|
||||
})
|
||||
export class BitcoinsatoshisPipe implements PipeTransform {
|
||||
|
||||
constructor(private sanitizer: DomSanitizer) { }
|
||||
|
||||
transform(value: string): SafeHtml {
|
||||
const newValue = this.insertSpaces(parseFloat(value || '0').toFixed(8));
|
||||
const position = (newValue || '0').search(/[1-9]/);
|
||||
|
||||
const firstPart = newValue.slice(0, position);
|
||||
const secondPart = newValue.slice(position);
|
||||
|
||||
return this.sanitizer.bypassSecurityTrustHtml(
|
||||
`<span class="text-secondary">${firstPart}</span>${secondPart}`
|
||||
);
|
||||
}
|
||||
|
||||
insertSpaces(str: string): string {
|
||||
const length = str.length;
|
||||
return str.slice(0, length - 6) + ' ' + str.slice(length - 6, length - 3) + ' ' + str.slice(length - 3);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -309,3 +309,28 @@ export function takeWhile(input: any[], predicate: CollectionPredicate) {
|
||||
return takeUntil(input, (item: any, index: number | undefined, collection: any[] | undefined) =>
|
||||
!predicate(item, index, collection));
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
|
||||
export function hasTouchScreen(): boolean {
|
||||
let hasTouchScreen = false;
|
||||
if ('maxTouchPoints' in navigator) {
|
||||
hasTouchScreen = navigator.maxTouchPoints > 0;
|
||||
} else if ('msMaxTouchPoints' in navigator) {
|
||||
// @ts-ignore
|
||||
hasTouchScreen = navigator.msMaxTouchPoints > 0;
|
||||
} else {
|
||||
const mQ = matchMedia?.('(pointer:coarse)');
|
||||
if (mQ?.media === '(pointer:coarse)') {
|
||||
hasTouchScreen = !!mQ.matches;
|
||||
} else if ('orientation' in window) {
|
||||
hasTouchScreen = true; // deprecated, but good fallback
|
||||
} else {
|
||||
// @ts-ignore - Only as a last resort, fall back to user agent sniffing
|
||||
const UA = navigator.userAgent;
|
||||
hasTouchScreen =
|
||||
/\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) ||
|
||||
/\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA);
|
||||
}
|
||||
}
|
||||
return hasTouchScreen;
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export class WuBytesPipe implements PipeTransform {
|
||||
'TWU': {max: Number.MAX_SAFE_INTEGER, prev: 'GWU'}
|
||||
};
|
||||
|
||||
transform(input: any, decimal: number = 0, from: ByteUnit = 'WU', to?: ByteUnit): any {
|
||||
transform(input: any, decimal: number = 0, from: ByteUnit = 'WU', to?: ByteUnit, plainText?: boolean): any {
|
||||
|
||||
if (!(isNumberFinite(input) &&
|
||||
isNumberFinite(decimal) &&
|
||||
@@ -38,7 +38,7 @@ export class WuBytesPipe implements PipeTransform {
|
||||
|
||||
const result = toDecimal(WuBytesPipe.calculateResult(format, bytes), decimal);
|
||||
|
||||
return WuBytesPipe.formatResult(result, to);
|
||||
return WuBytesPipe.formatResult(result, to, plainText);
|
||||
}
|
||||
|
||||
for (const key in WuBytesPipe.formats) {
|
||||
@@ -47,12 +47,15 @@ export class WuBytesPipe implements PipeTransform {
|
||||
|
||||
const result = toDecimal(WuBytesPipe.calculateResult(format, bytes), decimal);
|
||||
|
||||
return WuBytesPipe.formatResult(result, key);
|
||||
return WuBytesPipe.formatResult(result, key, plainText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static formatResult(result: number, unit: string): string {
|
||||
static formatResult(result: number, unit: string, plainText: boolean): string {
|
||||
if (plainText){
|
||||
return `${result} ${unit}`;
|
||||
}
|
||||
return `${result} <span class="symbol">${unit}</span>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,11 @@ export class FeeRoundingPipe implements PipeTransform {
|
||||
@Inject(LOCALE_ID) private locale: string,
|
||||
) {}
|
||||
|
||||
transform(fee: number): string {
|
||||
transform(fee: number, rounding = null): string {
|
||||
if (rounding) {
|
||||
return formatNumber(fee, this.locale, rounding);
|
||||
}
|
||||
|
||||
if (fee >= 100) {
|
||||
return formatNumber(fee, this.locale, '1.0-0')
|
||||
} else if (fee < 10) {
|
||||
|
||||
@@ -35,6 +35,7 @@ import { TxFeeRatingComponent } from '../components/tx-fee-rating/tx-fee-rating.
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { LanguageSelectorComponent } from '../components/language-selector/language-selector.component';
|
||||
import { FiatSelectorComponent } from '../components/fiat-selector/fiat-selector.component';
|
||||
import { RateUnitSelectorComponent } from '../components/rate-unit-selector/rate-unit-selector.component';
|
||||
import { ColoredPriceDirective } from './directives/colored-price.directive';
|
||||
import { NoSanitizePipe } from './pipes/no-sanitize.pipe';
|
||||
import { MempoolBlocksComponent } from '../components/mempool-blocks/mempool-blocks.component';
|
||||
@@ -82,6 +83,7 @@ import { IndexingProgressComponent } from '../components/indexing-progress/index
|
||||
import { SvgImagesComponent } from '../components/svg-images/svg-images.component';
|
||||
import { ChangeComponent } from '../components/change/change.component';
|
||||
import { SatsComponent } from './components/sats/sats.component';
|
||||
import { FeeRateComponent } from './components/fee-rate/fee-rate.component';
|
||||
import { TruncateComponent } from './components/truncate/truncate.component';
|
||||
import { SearchResultsComponent } from '../components/search-form/search-results/search-results.component';
|
||||
import { TimestampComponent } from './components/timestamp/timestamp.component';
|
||||
@@ -95,6 +97,10 @@ import { MempoolBlockOverviewComponent } from '../components/mempool-block-overv
|
||||
import { ClockchainComponent } from '../components/clockchain/clockchain.component';
|
||||
import { ClockFaceComponent } from '../components/clock-face/clock-face.component';
|
||||
import { ClockComponent } from '../components/clock/clock.component';
|
||||
import { CalculatorComponent } from '../components/calculator/calculator.component';
|
||||
import { BitcoinsatoshisPipe } from '../shared/pipes/bitcoinsatoshis.pipe';
|
||||
|
||||
import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-directives/weight-directives';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -106,6 +112,7 @@ import { ClockComponent } from '../components/clock/clock.component';
|
||||
TxFeeRatingComponent,
|
||||
LanguageSelectorComponent,
|
||||
FiatSelectorComponent,
|
||||
RateUnitSelectorComponent,
|
||||
ScriptpubkeyTypePipe,
|
||||
RelativeUrlPipe,
|
||||
NoSanitizePipe,
|
||||
@@ -171,6 +178,7 @@ import { ClockComponent } from '../components/clock/clock.component';
|
||||
SvgImagesComponent,
|
||||
ChangeComponent,
|
||||
SatsComponent,
|
||||
FeeRateComponent,
|
||||
TruncateComponent,
|
||||
SearchResultsComponent,
|
||||
TimestampComponent,
|
||||
@@ -179,11 +187,14 @@ import { ClockComponent } from '../components/clock/clock.component';
|
||||
GeolocationComponent,
|
||||
TestnetAlertComponent,
|
||||
GlobalFooterComponent,
|
||||
|
||||
CalculatorComponent,
|
||||
BitcoinsatoshisPipe,
|
||||
MempoolBlockOverviewComponent,
|
||||
ClockchainComponent,
|
||||
ClockComponent,
|
||||
ClockFaceComponent,
|
||||
OnlyVsizeDirective,
|
||||
OnlyWeightDirective
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@@ -200,6 +211,7 @@ import { ClockComponent } from '../components/clock/clock.component';
|
||||
],
|
||||
providers: [
|
||||
VbytesPipe,
|
||||
WuBytesPipe,
|
||||
RelativeUrlPipe,
|
||||
NoSanitizePipe,
|
||||
ShortenStringPipe,
|
||||
@@ -225,6 +237,7 @@ import { ClockComponent } from '../components/clock/clock.component';
|
||||
TxFeeRatingComponent,
|
||||
LanguageSelectorComponent,
|
||||
FiatSelectorComponent,
|
||||
RateUnitSelectorComponent,
|
||||
ScriptpubkeyTypePipe,
|
||||
RelativeUrlPipe,
|
||||
Hex2asciiPipe,
|
||||
@@ -284,6 +297,7 @@ import { ClockComponent } from '../components/clock/clock.component';
|
||||
SvgImagesComponent,
|
||||
ChangeComponent,
|
||||
SatsComponent,
|
||||
FeeRateComponent,
|
||||
TruncateComponent,
|
||||
SearchResultsComponent,
|
||||
TimestampComponent,
|
||||
@@ -297,6 +311,9 @@ import { ClockComponent } from '../components/clock/clock.component';
|
||||
ClockchainComponent,
|
||||
ClockComponent,
|
||||
ClockFaceComponent,
|
||||
|
||||
OnlyVsizeDirective,
|
||||
OnlyWeightDirective
|
||||
]
|
||||
})
|
||||
export class SharedModule {
|
||||
|
||||
Reference in New Issue
Block a user