Merge pull request #4837 from mempool/mononaut/goggles-age-filter
Goggles age filter
This commit is contained in:
commit
3a8c46bbed
@ -14,14 +14,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-menu" *ngIf="menuOpen && cssWidth > 280">
|
<div class="filter-menu" *ngIf="menuOpen && cssWidth > 280">
|
||||||
<h5>Match</h5>
|
<div class="filter-row">
|
||||||
<div class="btn-group btn-group-toggle">
|
<div class="filter-element">
|
||||||
<label class="btn btn-xs blue mode-toggle" [class.active]="filterMode === 'and'">
|
<h5>Match</h5>
|
||||||
<input type="radio" [value]="'all'" fragment="all" (click)="setFilterMode('and')">All
|
<div class="btn-group btn-group-toggle">
|
||||||
</label>
|
<label class="btn btn-xs blue mode-toggle" [class.active]="filterMode === 'and'">
|
||||||
<label class="btn btn-xs green mode-toggle" [class.active]="filterMode === 'or'">
|
<input type="radio" [value]="'all'" fragment="all" (click)="setFilterMode('and')">All
|
||||||
<input type="radio" [value]="'any'" fragment="any" (click)="setFilterMode('or')">Any
|
</label>
|
||||||
</label>
|
<label class="btn btn-xs green mode-toggle" [class.active]="filterMode === 'or'">
|
||||||
|
<input type="radio" [value]="'any'" fragment="any" (click)="setFilterMode('or')">Any
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="filter-element">
|
||||||
|
<h5>Gradient</h5>
|
||||||
|
<div class="btn-group btn-group-toggle">
|
||||||
|
<label class="btn btn-xs yellow mode-toggle" [class.active]="gradientMode === 'fee'">
|
||||||
|
<input type="radio" [value]="'fee'" fragment="default" (click)="setGradientMode('fee')">Default
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-xs blue mode-toggle" [class.active]="gradientMode === 'age'">
|
||||||
|
<input type="radio" [value]="'age'" fragment="age" (click)="setGradientMode('age')">Age
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngFor="let group of filterGroups;">
|
<ng-container *ngFor="let group of filterGroups;">
|
||||||
<h5>{{ group.label }}</h5>
|
<h5>{{ group.label }}</h5>
|
||||||
|
@ -45,6 +45,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.filter-menu {
|
.filter-menu {
|
||||||
|
.filter-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: start;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: white;
|
color: white;
|
||||||
@ -118,6 +125,12 @@
|
|||||||
background: #1a9436;
|
background: #1a9436;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.yellow {
|
||||||
|
border: solid 1px #bf7815;
|
||||||
|
&.active {
|
||||||
|
background: #bf7815;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:host-context(.block-overview-graph:hover) &, &:hover, &:active {
|
:host-context(.block-overview-graph:hover) &, &:hover, &:active {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, EventEmitter, Output, HostListener, Input, ChangeDetectorRef, OnChanges, SimpleChanges, OnInit, OnDestroy } from '@angular/core';
|
import { Component, EventEmitter, Output, HostListener, Input, ChangeDetectorRef, OnChanges, SimpleChanges, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { ActiveFilter, FilterGroups, FilterMode, TransactionFilters } from '../../shared/filters.utils';
|
import { ActiveFilter, FilterGroups, FilterMode, GradientMode, TransactionFilters } from '../../shared/filters.utils';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
@ -22,6 +22,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
activeFilters: string[] = [];
|
activeFilters: string[] = [];
|
||||||
filterFlags: { [key: string]: boolean } = {};
|
filterFlags: { [key: string]: boolean } = {};
|
||||||
filterMode: FilterMode = 'and';
|
filterMode: FilterMode = 'and';
|
||||||
|
gradientMode: GradientMode = 'fee';
|
||||||
menuOpen: boolean = false;
|
menuOpen: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -32,6 +33,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.filterSubscription = this.stateService.activeGoggles$.subscribe((active: ActiveFilter) => {
|
this.filterSubscription = this.stateService.activeGoggles$.subscribe((active: ActiveFilter) => {
|
||||||
this.filterMode = active.mode;
|
this.filterMode = active.mode;
|
||||||
|
this.gradientMode = active.gradient;
|
||||||
for (const key of Object.keys(this.filterFlags)) {
|
for (const key of Object.keys(this.filterFlags)) {
|
||||||
this.filterFlags[key] = false;
|
this.filterFlags[key] = false;
|
||||||
}
|
}
|
||||||
@ -39,7 +41,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.filterFlags[key] = !this.disabledFilters[key];
|
this.filterFlags[key] = !this.disabledFilters[key];
|
||||||
}
|
}
|
||||||
this.activeFilters = [...active.filters.filter(key => !this.disabledFilters[key])];
|
this.activeFilters = [...active.filters.filter(key => !this.disabledFilters[key])];
|
||||||
this.onFilterChanged.emit({ mode: active.mode, filters: this.activeFilters });
|
this.onFilterChanged.emit({ mode: active.mode, filters: this.activeFilters, gradient: this.gradientMode });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,8 +59,14 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
setFilterMode(mode): void {
|
setFilterMode(mode): void {
|
||||||
this.filterMode = mode;
|
this.filterMode = mode;
|
||||||
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters });
|
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters, gradient: this.gradientMode });
|
||||||
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] });
|
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters], gradient: this.gradientMode });
|
||||||
|
}
|
||||||
|
|
||||||
|
setGradientMode(mode): void {
|
||||||
|
this.gradientMode = mode;
|
||||||
|
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters, gradient: this.gradientMode });
|
||||||
|
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters], gradient: this.gradientMode });
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleFilter(key): void {
|
toggleFilter(key): void {
|
||||||
@ -81,8 +89,8 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.activeFilters = this.activeFilters.filter(f => f != key);
|
this.activeFilters = this.activeFilters.filter(f => f != key);
|
||||||
}
|
}
|
||||||
const booleanFlags = this.getBooleanFlags();
|
const booleanFlags = this.getBooleanFlags();
|
||||||
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters });
|
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters, gradient: this.gradientMode });
|
||||||
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] });
|
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters], gradient: this.gradientMode });
|
||||||
}
|
}
|
||||||
|
|
||||||
getBooleanFlags(): bigint | null {
|
getBooleanFlags(): bigint | null {
|
||||||
|
@ -8,14 +8,11 @@ import { Color, Position } from './sprite-types';
|
|||||||
import { Price } from '../../services/price.service';
|
import { Price } from '../../services/price.service';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { defaultColorFunction, setOpacity, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils';
|
import { defaultColorFunction, setOpacity, defaultAuditColors, defaultColors, ageColorFunction } from './utils';
|
||||||
import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils';
|
import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils';
|
||||||
import { detectWebGL } from '../../shared/graphs.utils';
|
import { detectWebGL } from '../../shared/graphs.utils';
|
||||||
|
|
||||||
const unmatchedOpacity = 0.2;
|
const unmatchedOpacity = 0.2;
|
||||||
const unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity));
|
|
||||||
const unmatchedAuditFeeColors = defaultAuditFeeColors.map(c => setOpacity(c, unmatchedOpacity));
|
|
||||||
const unmatchedMarginalFeeColors = defaultMarginalFeeColors.map(c => setOpacity(c, unmatchedOpacity));
|
|
||||||
const unmatchedAuditColors = {
|
const unmatchedAuditColors = {
|
||||||
censored: setOpacity(defaultAuditColors.censored, unmatchedOpacity),
|
censored: setOpacity(defaultAuditColors.censored, unmatchedOpacity),
|
||||||
missing: setOpacity(defaultAuditColors.missing, unmatchedOpacity),
|
missing: setOpacity(defaultAuditColors.missing, unmatchedOpacity),
|
||||||
@ -46,6 +43,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
@Input() excludeFilters: string[] = [];
|
@Input() excludeFilters: string[] = [];
|
||||||
@Input() filterFlags: bigint | null = null;
|
@Input() filterFlags: bigint | null = null;
|
||||||
@Input() filterMode: FilterMode = 'and';
|
@Input() filterMode: FilterMode = 'and';
|
||||||
|
@Input() gradientMode: 'fee' | 'age' = 'fee';
|
||||||
@Input() relativeTime: number | null;
|
@Input() relativeTime: number | null;
|
||||||
@Input() blockConversion: Price;
|
@Input() blockConversion: Price;
|
||||||
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
|
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
|
||||||
@ -121,21 +119,22 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
this.setHighlightingEnabled(this.auditHighlighting);
|
this.setHighlightingEnabled(this.auditHighlighting);
|
||||||
}
|
}
|
||||||
if (changes.overrideColor && this.scene) {
|
if (changes.overrideColor && this.scene) {
|
||||||
this.scene.setColorFunction(this.overrideColors);
|
this.scene.setColorFunction(this.getFilterColorFunction(0n, this.gradientMode));
|
||||||
}
|
}
|
||||||
if ((changes.filterFlags || changes.showFilters || changes.filterMode)) {
|
if ((changes.filterFlags || changes.showFilters || changes.filterMode || changes.gradientMode)) {
|
||||||
this.setFilterFlags();
|
this.setFilterFlags();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setFilterFlags(goggle?: ActiveFilter): void {
|
setFilterFlags(goggle?: ActiveFilter): void {
|
||||||
this.filterMode = goggle?.mode || this.filterMode;
|
this.filterMode = goggle?.mode || this.filterMode;
|
||||||
|
this.gradientMode = goggle?.gradient || this.gradientMode;
|
||||||
this.activeFilterFlags = goggle?.filters ? toFlags(goggle.filters) : this.filterFlags;
|
this.activeFilterFlags = goggle?.filters ? toFlags(goggle.filters) : this.filterFlags;
|
||||||
if (this.scene) {
|
if (this.scene) {
|
||||||
if (this.activeFilterFlags != null && this.filtersAvailable) {
|
if (this.activeFilterFlags != null && this.filtersAvailable) {
|
||||||
this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags));
|
this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags, this.gradientMode));
|
||||||
} else {
|
} else {
|
||||||
this.scene.setColorFunction(this.overrideColors);
|
this.scene.setColorFunction(this.getFilterColorFunction(0n, this.gradientMode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.start();
|
this.start();
|
||||||
@ -212,6 +211,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
remove = remove.filter(txid => this.scene.txs[txid]);
|
remove = remove.filter(txid => this.scene.txs[txid]);
|
||||||
change = change.filter(tx => this.scene.txs[tx.txid]);
|
change = change.filter(tx => this.scene.txs[tx.txid]);
|
||||||
|
|
||||||
|
if (this.gradientMode === 'age') {
|
||||||
|
this.scene.updateAllColors();
|
||||||
|
}
|
||||||
this.scene.update(add, remove, change, direction, resetLayout);
|
this.scene.update(add, remove, change, direction, resetLayout);
|
||||||
this.start();
|
this.start();
|
||||||
this.updateSearchHighlight();
|
this.updateSearchHighlight();
|
||||||
@ -548,25 +550,24 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
|
|
||||||
getColorFunction(): ((tx: TxView) => Color) {
|
getColorFunction(): ((tx: TxView) => Color) {
|
||||||
if (this.filterFlags) {
|
if (this.filterFlags) {
|
||||||
return this.getFilterColorFunction(this.filterFlags);
|
return this.getFilterColorFunction(this.filterFlags, this.gradientMode);
|
||||||
} else if (this.activeFilterFlags) {
|
} else if (this.activeFilterFlags) {
|
||||||
return this.getFilterColorFunction(this.activeFilterFlags);
|
return this.getFilterColorFunction(this.activeFilterFlags, this.gradientMode);
|
||||||
} else {
|
} else {
|
||||||
return this.overrideColors;
|
return this.getFilterColorFunction(0n, this.gradientMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) {
|
getFilterColorFunction(flags: bigint, gradient: 'fee' | 'age'): ((tx: TxView) => Color) {
|
||||||
return (tx: TxView) => {
|
return (tx: TxView) => {
|
||||||
if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) {
|
if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) {
|
||||||
return defaultColorFunction(tx);
|
return (gradient === 'age') ? ageColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)) : defaultColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000));
|
||||||
} else {
|
} else {
|
||||||
return defaultColorFunction(
|
return (gradient === 'age') ? { r: 1, g: 1, b: 1, a: 0.05 } : defaultColorFunction(
|
||||||
tx,
|
tx,
|
||||||
unmatchedFeeColors,
|
defaultColors.unmatchedfee,
|
||||||
unmatchedAuditFeeColors,
|
unmatchedAuditColors,
|
||||||
unmatchedMarginalFeeColors,
|
this.relativeTime || (Date.now() / 1000)
|
||||||
unmatchedAuditColors
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -68,6 +68,10 @@ export default class BlockScene {
|
|||||||
|
|
||||||
setColorFunction(colorFunction: ((tx: TxView) => Color) | null): void {
|
setColorFunction(colorFunction: ((tx: TxView) => Color) | null): void {
|
||||||
this.getColor = colorFunction || defaultColorFunction;
|
this.getColor = colorFunction || defaultColorFunction;
|
||||||
|
this.updateAllColors();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAllColors(): void {
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
if (this.initialised && this.scene) {
|
if (this.initialised && this.scene) {
|
||||||
this.updateColors(performance.now(), 50);
|
this.updateColors(performance.now(), 50);
|
||||||
|
@ -37,10 +37,36 @@ export function setOpacity(color: Color, opacity: number): Color {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ColorPalette {
|
||||||
|
base: Color[],
|
||||||
|
audit: Color[],
|
||||||
|
marginal: Color[],
|
||||||
|
baseLevel: (tx: TxView, rate: number, time: number) => number,
|
||||||
|
}
|
||||||
|
|
||||||
// precomputed colors
|
// precomputed colors
|
||||||
export const defaultFeeColors = mempoolFeeColors.map(hexToColor);
|
const defaultColors: { [key: string]: ColorPalette } = {
|
||||||
export const defaultAuditFeeColors = defaultFeeColors.map((color) => darken(desaturate(color, 0.3), 0.9));
|
fee: {
|
||||||
export const defaultMarginalFeeColors = defaultFeeColors.map((color) => darken(desaturate(color, 0.8), 1.1));
|
base: mempoolFeeColors.map(hexToColor),
|
||||||
|
audit: [],
|
||||||
|
marginal: [],
|
||||||
|
baseLevel: (tx: TxView, rate: number) => feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for (const key in defaultColors) {
|
||||||
|
const base = defaultColors[key].base;
|
||||||
|
defaultColors[key].audit = base.map((color) => darken(desaturate(color, 0.3), 0.9));
|
||||||
|
defaultColors[key].marginal = base.map((color) => darken(desaturate(color, 0.8), 1.1));
|
||||||
|
defaultColors['unmatched' + key] = {
|
||||||
|
base: defaultColors[key].base.map(c => setOpacity(c, 0.2)),
|
||||||
|
audit: defaultColors[key].audit.map(c => setOpacity(c, 0.2)),
|
||||||
|
marginal: defaultColors[key].marginal.map(c => setOpacity(c, 0.2)),
|
||||||
|
baseLevel: defaultColors[key].baseLevel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { defaultColors as defaultColors };
|
||||||
|
|
||||||
export const defaultAuditColors = {
|
export const defaultAuditColors = {
|
||||||
censored: hexToColor('f344df'),
|
censored: hexToColor('f344df'),
|
||||||
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
||||||
@ -51,22 +77,21 @@ export const defaultAuditColors = {
|
|||||||
|
|
||||||
export function defaultColorFunction(
|
export function defaultColorFunction(
|
||||||
tx: TxView,
|
tx: TxView,
|
||||||
feeColors: Color[] = defaultFeeColors,
|
colors: { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number } = defaultColors.fee,
|
||||||
auditFeeColors: Color[] = defaultAuditFeeColors,
|
auditColors: { [status: string]: Color } = defaultAuditColors,
|
||||||
marginalFeeColors: Color[] = defaultMarginalFeeColors,
|
relativeTime?: number,
|
||||||
auditColors: { [status: string]: Color } = defaultAuditColors
|
|
||||||
): Color {
|
): Color {
|
||||||
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
|
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
|
||||||
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1;
|
const levelIndex = colors.baseLevel(tx, rate, relativeTime || (Date.now() / 1000));
|
||||||
const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1];
|
const levelColor = colors.base[levelIndex] || colors.base[mempoolFeeColors.length - 1];
|
||||||
// Normal mode
|
// Normal mode
|
||||||
if (!tx.scene?.highlightingEnabled) {
|
if (!tx.scene?.highlightingEnabled) {
|
||||||
if (tx.acc) {
|
if (tx.acc) {
|
||||||
return auditColors.accelerated;
|
return auditColors.accelerated;
|
||||||
} else {
|
} else {
|
||||||
return feeLevelColor;
|
return levelColor;
|
||||||
}
|
}
|
||||||
return feeLevelColor;
|
return levelColor;
|
||||||
}
|
}
|
||||||
// Block audit
|
// Block audit
|
||||||
switch(tx.status) {
|
switch(tx.status) {
|
||||||
@ -75,7 +100,7 @@ export function defaultColorFunction(
|
|||||||
case 'missing':
|
case 'missing':
|
||||||
case 'sigop':
|
case 'sigop':
|
||||||
case 'rbf':
|
case 'rbf':
|
||||||
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
return colors.marginal[levelIndex] || colors.marginal[mempoolFeeColors.length - 1];
|
||||||
case 'fresh':
|
case 'fresh':
|
||||||
case 'freshcpfp':
|
case 'freshcpfp':
|
||||||
return auditColors.missing;
|
return auditColors.missing;
|
||||||
@ -84,20 +109,37 @@ export function defaultColorFunction(
|
|||||||
case 'prioritized':
|
case 'prioritized':
|
||||||
return auditColors.prioritized;
|
return auditColors.prioritized;
|
||||||
case 'selected':
|
case 'selected':
|
||||||
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
return colors.marginal[levelIndex] || colors.marginal[mempoolFeeColors.length - 1];
|
||||||
case 'accelerated':
|
case 'accelerated':
|
||||||
return auditColors.accelerated;
|
return auditColors.accelerated;
|
||||||
case 'found':
|
case 'found':
|
||||||
if (tx.context === 'projected') {
|
if (tx.context === 'projected') {
|
||||||
return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
|
return colors.audit[levelIndex] || colors.audit[mempoolFeeColors.length - 1];
|
||||||
} else {
|
} else {
|
||||||
return feeLevelColor;
|
return levelColor;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if (tx.acc) {
|
if (tx.acc) {
|
||||||
return auditColors.accelerated;
|
return auditColors.accelerated;
|
||||||
} else {
|
} else {
|
||||||
return feeLevelColor;
|
return levelColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ageColorFunction(
|
||||||
|
tx: TxView,
|
||||||
|
colors: { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number } = defaultColors.fee,
|
||||||
|
auditColors: { [status: string]: Color } = defaultAuditColors,
|
||||||
|
relativeTime?: number,
|
||||||
|
): Color {
|
||||||
|
const color = defaultColorFunction(tx, colors, auditColors, relativeTime);
|
||||||
|
|
||||||
|
const ageLevel = (!tx.time ? 0 : (0.8 * Math.tanh((1 / 15) * Math.log2((Math.max(1, 0.6 * ((relativeTime - tx.time) - 60)))))));
|
||||||
|
return {
|
||||||
|
r: color.r,
|
||||||
|
g: color.g,
|
||||||
|
b: color.b,
|
||||||
|
a: color.a * (1 - ageLevel)
|
||||||
|
};
|
||||||
}
|
}
|
@ -7,7 +7,7 @@ import { ApiService } from '../services/api.service';
|
|||||||
import { StateService } from '../services/state.service';
|
import { StateService } from '../services/state.service';
|
||||||
import { WebsocketService } from '../services/websocket.service';
|
import { WebsocketService } from '../services/websocket.service';
|
||||||
import { SeoService } from '../services/seo.service';
|
import { SeoService } from '../services/seo.service';
|
||||||
import { ActiveFilter, FilterMode, toFlags } from '../shared/filters.utils';
|
import { ActiveFilter, FilterMode, GradientMode, toFlags } from '../shared/filters.utils';
|
||||||
import { detectWebGL } from '../shared/graphs.utils';
|
import { detectWebGL } from '../shared/graphs.utils';
|
||||||
|
|
||||||
interface MempoolBlocksData {
|
interface MempoolBlocksData {
|
||||||
@ -74,14 +74,15 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
private lastReservesBlockUpdate: number = 0;
|
private lastReservesBlockUpdate: number = 0;
|
||||||
|
|
||||||
goggleResolution = 82;
|
goggleResolution = 82;
|
||||||
goggleCycle: { index: number, name: string, mode: FilterMode, filters: string[] }[] = [
|
goggleCycle: { index: number, name: string, mode: FilterMode, filters: string[], gradient: GradientMode }[] = [
|
||||||
{ index: 0, name: 'All', mode: 'and', filters: [] },
|
{ index: 0, name: 'All', mode: 'and', filters: [], gradient: 'fee' },
|
||||||
{ index: 1, name: 'Consolidation', mode: 'and', filters: ['consolidation'] },
|
{ index: 1, name: 'Consolidation', mode: 'and', filters: ['consolidation'], gradient: 'fee' },
|
||||||
{ index: 2, name: 'Coinjoin', mode: 'and', filters: ['coinjoin'] },
|
{ index: 2, name: 'Coinjoin', mode: 'and', filters: ['coinjoin'], gradient: 'fee' },
|
||||||
{ index: 3, name: 'Data', mode: 'or', filters: ['inscription', 'fake_pubkey', 'op_return'] },
|
{ index: 3, name: 'Data', mode: 'or', filters: ['inscription', 'fake_pubkey', 'op_return'], gradient: 'fee' },
|
||||||
];
|
];
|
||||||
goggleFlags = 0n;
|
goggleFlags = 0n;
|
||||||
goggleMode: FilterMode = 'and';
|
goggleMode: FilterMode = 'and';
|
||||||
|
gradientMode: GradientMode = 'fee';
|
||||||
goggleIndex = 0;
|
goggleIndex = 0;
|
||||||
|
|
||||||
private destroy$ = new Subject();
|
private destroy$ = new Subject();
|
||||||
@ -131,6 +132,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
this.goggleIndex = goggle.index;
|
this.goggleIndex = goggle.index;
|
||||||
this.goggleFlags = toFlags(goggle.filters);
|
this.goggleFlags = toFlags(goggle.filters);
|
||||||
this.goggleMode = goggle.mode;
|
this.goggleMode = goggle.mode;
|
||||||
|
this.gradientMode = goggle.gradient;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,6 +142,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
name: 'Custom',
|
name: 'Custom',
|
||||||
mode: active.mode,
|
mode: active.mode,
|
||||||
filters: active.filters,
|
filters: active.filters,
|
||||||
|
gradient: active.gradient,
|
||||||
});
|
});
|
||||||
this.goggleIndex = this.goggleCycle.length - 1;
|
this.goggleIndex = this.goggleCycle.length - 1;
|
||||||
this.goggleFlags = toFlags(active.filters);
|
this.goggleFlags = toFlags(active.filters);
|
||||||
|
@ -154,7 +154,7 @@ export class StateService {
|
|||||||
searchFocus$: Subject<boolean> = new Subject<boolean>();
|
searchFocus$: Subject<boolean> = new Subject<boolean>();
|
||||||
menuOpen$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
menuOpen$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||||
|
|
||||||
activeGoggles$: BehaviorSubject<ActiveFilter> = new BehaviorSubject({ mode: 'and', filters: [] });
|
activeGoggles$: BehaviorSubject<ActiveFilter> = new BehaviorSubject({ mode: 'and', filters: [], gradient: 'fee' });
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(PLATFORM_ID) private platformId: any,
|
@Inject(PLATFORM_ID) private platformId: any,
|
||||||
|
@ -11,9 +11,12 @@ export interface Filter {
|
|||||||
|
|
||||||
export type FilterMode = 'and' | 'or';
|
export type FilterMode = 'and' | 'or';
|
||||||
|
|
||||||
|
export type GradientMode = 'fee' | 'age';
|
||||||
|
|
||||||
export interface ActiveFilter {
|
export interface ActiveFilter {
|
||||||
mode: FilterMode,
|
mode: FilterMode,
|
||||||
filters: string[],
|
filters: string[],
|
||||||
|
gradient: GradientMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
// binary flags for transaction classification
|
// binary flags for transaction classification
|
||||||
|
Loading…
x
Reference in New Issue
Block a user