Make dashboard filters persistent, add disjunctive filter mode
This commit is contained in:
		
							parent
							
								
									dfbec0ceef
								
							
						
					
					
						commit
						ddee5f927c
					
				@ -1,4 +1,4 @@
 | 
				
			|||||||
<div class="block-filters" [class.filters-active]="activeFilters.length > 0" [class.menu-open]="menuOpen" [class.small]="cssWidth < 500" [class.vsmall]="cssWidth < 400" [class.tiny]="cssWidth < 200">
 | 
					<div class="block-filters" [class.filters-active]="activeFilters.length > 0" [class.any-mode]="filterMode === 'or'" [class.menu-open]="menuOpen" [class.small]="cssWidth < 500" [class.vsmall]="cssWidth < 400" [class.tiny]="cssWidth < 200">
 | 
				
			||||||
  <a *ngIf="menuOpen" [routerLink]="['/docs/faq' | relativeUrl]" fragment="how-do-mempool-goggles-work" class="info-badges" i18n-ngbTooltip="Mempool Goggles tooltip" ngbTooltip="select filter categories to highlight matching transactions">
 | 
					  <a *ngIf="menuOpen" [routerLink]="['/docs/faq' | relativeUrl]" fragment="how-do-mempool-goggles-work" class="info-badges" i18n-ngbTooltip="Mempool Goggles tooltip" ngbTooltip="select filter categories to highlight matching transactions">
 | 
				
			||||||
    <span class="badge badge-pill badge-warning beta" i18n="beta">beta</span>
 | 
					    <span class="badge badge-pill badge-warning beta" i18n="beta">beta</span>
 | 
				
			||||||
    <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" size="lg"></fa-icon>
 | 
					    <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" size="lg"></fa-icon>
 | 
				
			||||||
@ -14,6 +14,15 @@
 | 
				
			|||||||
    </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="btn-group btn-group-toggle">
 | 
				
			||||||
 | 
					      <label class="btn btn-xs blue mode-toggle" [class.active]="filterMode === 'and'">
 | 
				
			||||||
 | 
					        <input type="radio" [value]="'all'" fragment="all" (click)="setFilterMode('and')">All
 | 
				
			||||||
 | 
					      </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>
 | 
				
			||||||
    <ng-container *ngFor="let group of filterGroups;">
 | 
					    <ng-container *ngFor="let group of filterGroups;">
 | 
				
			||||||
      <h5>{{ group.label }}</h5>
 | 
					      <h5>{{ group.label }}</h5>
 | 
				
			||||||
      <div class="filter-group">
 | 
					      <div class="filter-group">
 | 
				
			||||||
 | 
				
			|||||||
@ -77,6 +77,49 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  &.any-mode {
 | 
				
			||||||
 | 
					    .filter-tag {
 | 
				
			||||||
 | 
					      border: solid 1px #1a9436;
 | 
				
			||||||
 | 
					      &.selected {
 | 
				
			||||||
 | 
					        background-color: #1a9436;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .btn-group {
 | 
				
			||||||
 | 
					    font-size: 0.9em;
 | 
				
			||||||
 | 
					    margin-right: 0.25em;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .mode-toggle {
 | 
				
			||||||
 | 
					    padding: 0.2em 0.5em;
 | 
				
			||||||
 | 
					    pointer-events: all;
 | 
				
			||||||
 | 
					    line-height: 1.5;
 | 
				
			||||||
 | 
					    background: #181b2daf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &:first-child {
 | 
				
			||||||
 | 
					      border-top-left-radius: 0.2rem;
 | 
				
			||||||
 | 
					      border-bottom-left-radius: 0.2rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    &:last-child {
 | 
				
			||||||
 | 
					      border-top-right-radius: 0.2rem;
 | 
				
			||||||
 | 
					      border-bottom-right-radius: 0.2rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.blue {
 | 
				
			||||||
 | 
					      border: solid 1px #105fb0;
 | 
				
			||||||
 | 
					      &.active {
 | 
				
			||||||
 | 
					        background: #105fb0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    &.green {
 | 
				
			||||||
 | 
					      border: solid 1px #1a9436;
 | 
				
			||||||
 | 
					      &.active {
 | 
				
			||||||
 | 
					        background: #1a9436;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  :host-context(.block-overview-graph:hover) &, &:hover, &:active {
 | 
					  :host-context(.block-overview-graph:hover) &, &:hover, &:active {
 | 
				
			||||||
    .menu-toggle {
 | 
					    .menu-toggle {
 | 
				
			||||||
      opacity: 0.5;
 | 
					      opacity: 0.5;
 | 
				
			||||||
 | 
				
			|||||||
@ -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 { FilterGroups, TransactionFilters } from '../../shared/filters.utils';
 | 
					import { ActiveFilter, FilterGroups, FilterMode, 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';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -12,7 +12,7 @@ import { Subscription } from 'rxjs';
 | 
				
			|||||||
export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
 | 
					export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
 | 
				
			||||||
  @Input() cssWidth: number = 800;
 | 
					  @Input() cssWidth: number = 800;
 | 
				
			||||||
  @Input() excludeFilters: string[] = [];
 | 
					  @Input() excludeFilters: string[] = [];
 | 
				
			||||||
  @Output() onFilterChanged: EventEmitter<bigint | null> = new EventEmitter();
 | 
					  @Output() onFilterChanged: EventEmitter<ActiveFilter | null> = new EventEmitter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  filterSubscription: Subscription;
 | 
					  filterSubscription: Subscription;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -21,6 +21,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
 | 
				
			|||||||
  disabledFilters: { [key: string]: boolean } = {};
 | 
					  disabledFilters: { [key: string]: boolean } = {};
 | 
				
			||||||
  activeFilters: string[] = [];
 | 
					  activeFilters: string[] = [];
 | 
				
			||||||
  filterFlags: { [key: string]: boolean } = {};
 | 
					  filterFlags: { [key: string]: boolean } = {};
 | 
				
			||||||
 | 
					  filterMode: FilterMode = 'and';
 | 
				
			||||||
  menuOpen: boolean = false;
 | 
					  menuOpen: boolean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
@ -29,15 +30,16 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
 | 
				
			|||||||
  ) {}
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngOnInit(): void {
 | 
					  ngOnInit(): void {
 | 
				
			||||||
    this.filterSubscription = this.stateService.activeGoggles$.subscribe((activeFilters: string[]) => {
 | 
					    this.filterSubscription = this.stateService.activeGoggles$.subscribe((active: ActiveFilter) => {
 | 
				
			||||||
 | 
					      this.filterMode = active.mode;
 | 
				
			||||||
      for (const key of Object.keys(this.filterFlags)) {
 | 
					      for (const key of Object.keys(this.filterFlags)) {
 | 
				
			||||||
        this.filterFlags[key] = false;
 | 
					        this.filterFlags[key] = false;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      for (const key of activeFilters) {
 | 
					      for (const key of active.filters) {
 | 
				
			||||||
        this.filterFlags[key] = !this.disabledFilters[key];
 | 
					        this.filterFlags[key] = !this.disabledFilters[key];
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.activeFilters = [...activeFilters.filter(key => !this.disabledFilters[key])];
 | 
					      this.activeFilters = [...active.filters.filter(key => !this.disabledFilters[key])];
 | 
				
			||||||
      this.onFilterChanged.emit(this.getBooleanFlags());
 | 
					      this.onFilterChanged.emit({ mode: active.mode, filters: this.activeFilters });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -53,6 +55,12 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setFilterMode(mode): void {
 | 
				
			||||||
 | 
					    this.filterMode = mode;
 | 
				
			||||||
 | 
					    this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters });
 | 
				
			||||||
 | 
					    this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  toggleFilter(key): void {
 | 
					  toggleFilter(key): void {
 | 
				
			||||||
    const filter = this.filters[key];
 | 
					    const filter = this.filters[key];
 | 
				
			||||||
    this.filterFlags[key] = !this.filterFlags[key];
 | 
					    this.filterFlags[key] = !this.filterFlags[key];
 | 
				
			||||||
@ -73,8 +81,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(booleanFlags);
 | 
					    this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters });
 | 
				
			||||||
    this.stateService.activeGoggles$.next([...this.activeFilters]);
 | 
					    this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  getBooleanFlags(): bigint | null {
 | 
					  getBooleanFlags(): bigint | null {
 | 
				
			||||||
@ -90,7 +98,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
 | 
				
			|||||||
  @HostListener('document:click', ['$event'])
 | 
					  @HostListener('document:click', ['$event'])
 | 
				
			||||||
  onClick(event): boolean {
 | 
					  onClick(event): boolean {
 | 
				
			||||||
    // click away from menu
 | 
					    // click away from menu
 | 
				
			||||||
    if (!event.target.closest('button')) {
 | 
					    if (!event.target.closest('button') && !event.target.closest('label')) {
 | 
				
			||||||
      this.menuOpen = false;
 | 
					      this.menuOpen = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ 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, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils';
 | 
				
			||||||
 | 
					import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const unmatchedOpacity = 0.2;
 | 
					const unmatchedOpacity = 0.2;
 | 
				
			||||||
const unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity));
 | 
					const unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity));
 | 
				
			||||||
@ -42,7 +43,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
 | 
				
			|||||||
  @Input() showFilters: boolean = false;
 | 
					  @Input() showFilters: boolean = false;
 | 
				
			||||||
  @Input() excludeFilters: string[] = [];
 | 
					  @Input() excludeFilters: string[] = [];
 | 
				
			||||||
  @Input() filterFlags: bigint | null = null;
 | 
					  @Input() filterFlags: bigint | null = null;
 | 
				
			||||||
  @Input() filterMode: 'and' | 'or' = 'and';
 | 
					  @Input() filterMode: FilterMode = 'and';
 | 
				
			||||||
  @Input() blockConversion: Price;
 | 
					  @Input() blockConversion: Price;
 | 
				
			||||||
  @Input() overrideColors: ((tx: TxView) => Color) | null = null;
 | 
					  @Input() overrideColors: ((tx: TxView) => Color) | null = null;
 | 
				
			||||||
  @Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>();
 | 
					  @Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>();
 | 
				
			||||||
@ -119,10 +120,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setFilterFlags(flags?: bigint | null): void {
 | 
					  setFilterFlags(goggle?: ActiveFilter): void {
 | 
				
			||||||
    this.activeFilterFlags = this.filterFlags || flags || null;
 | 
					    this.filterMode = goggle?.mode || this.filterMode;
 | 
				
			||||||
 | 
					    this.activeFilterFlags = goggle?.filters ? toFlags(goggle.filters) : this.filterFlags;
 | 
				
			||||||
    if (this.scene) {
 | 
					    if (this.scene) {
 | 
				
			||||||
      if (this.activeFilterFlags != null) {
 | 
					      if (this.activeFilterFlags != null && this.filtersAvailable) {
 | 
				
			||||||
        this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags));
 | 
					        this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags));
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this.scene.setColorFunction(this.overrideColors);
 | 
					        this.scene.setColorFunction(this.overrideColors);
 | 
				
			||||||
@ -157,7 +159,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // initialize the scene without any entry transition
 | 
					  // initialize the scene without any entry transition
 | 
				
			||||||
  setup(transactions: TransactionStripped[]): void {
 | 
					  setup(transactions: TransactionStripped[]): void {
 | 
				
			||||||
    this.filtersAvailable = transactions.reduce((flagSet, tx) => flagSet || tx.flags > 0, false);
 | 
					    const filtersAvailable = transactions.reduce((flagSet, tx) => flagSet || tx.flags > 0, false);
 | 
				
			||||||
 | 
					    if (filtersAvailable !== this.filtersAvailable) {
 | 
				
			||||||
 | 
					      this.setFilterFlags();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.filtersAvailable = filtersAvailable;
 | 
				
			||||||
    if (this.scene) {
 | 
					    if (this.scene) {
 | 
				
			||||||
      this.scene.setup(transactions);
 | 
					      this.scene.setup(transactions);
 | 
				
			||||||
      this.readyNextFrame = true;
 | 
					      this.readyNextFrame = true;
 | 
				
			||||||
@ -523,8 +529,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) {
 | 
					  getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) {
 | 
				
			||||||
 | 
					    console.log('getting filter color function: ', flags, this.filterMode);
 | 
				
			||||||
    return (tx: TxView) => {
 | 
					    return (tx: TxView) => {
 | 
				
			||||||
      if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (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 defaultColorFunction(tx);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        return defaultColorFunction(
 | 
					        return defaultColorFunction(
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,7 @@ import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pi
 | 
				
			|||||||
import { Router } from '@angular/router';
 | 
					import { Router } from '@angular/router';
 | 
				
			||||||
import { Color } from '../block-overview-graph/sprite-types';
 | 
					import { Color } from '../block-overview-graph/sprite-types';
 | 
				
			||||||
import TxView from '../block-overview-graph/tx-view';
 | 
					import TxView from '../block-overview-graph/tx-view';
 | 
				
			||||||
 | 
					import { FilterMode } from '../../shared/filters.utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'app-mempool-block-overview',
 | 
					  selector: 'app-mempool-block-overview',
 | 
				
			||||||
@ -22,7 +23,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
 | 
				
			|||||||
  @Input() showFilters: boolean = false;
 | 
					  @Input() showFilters: boolean = false;
 | 
				
			||||||
  @Input() overrideColors: ((tx: TxView) => Color) | null = null;
 | 
					  @Input() overrideColors: ((tx: TxView) => Color) | null = null;
 | 
				
			||||||
  @Input() filterFlags: bigint | undefined = undefined;
 | 
					  @Input() filterFlags: bigint | undefined = undefined;
 | 
				
			||||||
  @Input() filterMode: 'and' | 'or' = 'and';
 | 
					  @Input() filterMode: FilterMode = 'and';
 | 
				
			||||||
  @Output() txPreviewEvent = new EventEmitter<TransactionStripped | void>();
 | 
					  @Output() txPreviewEvent = new EventEmitter<TransactionStripped | void>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent;
 | 
					  @ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent;
 | 
				
			||||||
 | 
				
			|||||||
@ -26,7 +26,7 @@
 | 
				
			|||||||
            <div class="quick-filter">
 | 
					            <div class="quick-filter">
 | 
				
			||||||
              <div class="btn-group btn-group-toggle">
 | 
					              <div class="btn-group btn-group-toggle">
 | 
				
			||||||
                <label class="btn btn-primary btn-xs" [class.active]="filter.index === goggleIndex"  *ngFor="let filter of goggleCycle">
 | 
					                <label class="btn btn-primary btn-xs" [class.active]="filter.index === goggleIndex"  *ngFor="let filter of goggleCycle">
 | 
				
			||||||
                  <input type="radio" [value]="'3m'" fragment="3m" (click)="goggleIndex = filter.index" [attr.data-cy]="'3m'"> {{ filter.name }}
 | 
					                  <input type="radio" [value]="'3m'" fragment="3m" (click)="setFilter(filter.index)" [attr.data-cy]="'3m'"> {{ filter.name }}
 | 
				
			||||||
                </label>
 | 
					                </label>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
@ -34,8 +34,8 @@
 | 
				
			|||||||
              <app-mempool-block-overview
 | 
					              <app-mempool-block-overview
 | 
				
			||||||
                [index]="0"
 | 
					                [index]="0"
 | 
				
			||||||
                [resolution]="goggleResolution"
 | 
					                [resolution]="goggleResolution"
 | 
				
			||||||
                [filterFlags]="goggleCycle[goggleIndex].flag"
 | 
					                [filterFlags]="goggleFlags"
 | 
				
			||||||
                filterMode="or"
 | 
					                [filterMode]="goggleMode"
 | 
				
			||||||
              ></app-mempool-block-overview>
 | 
					              ></app-mempool-block-overview>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </ng-template>
 | 
					          </ng-template>
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +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';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface MempoolBlocksData {
 | 
					interface MempoolBlocksData {
 | 
				
			||||||
  blocks: number;
 | 
					  blocks: number;
 | 
				
			||||||
@ -55,6 +56,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
				
			|||||||
  currentReserves$: Observable<CurrentPegs>;
 | 
					  currentReserves$: Observable<CurrentPegs>;
 | 
				
			||||||
  fullHistory$: Observable<any>;
 | 
					  fullHistory$: Observable<any>;
 | 
				
			||||||
  isLoad: boolean = true;
 | 
					  isLoad: boolean = true;
 | 
				
			||||||
 | 
					  filterSubscription: Subscription;
 | 
				
			||||||
  mempoolInfoSubscription: Subscription;
 | 
					  mempoolInfoSubscription: Subscription;
 | 
				
			||||||
  currencySubscription: Subscription;
 | 
					  currencySubscription: Subscription;
 | 
				
			||||||
  currency: string;
 | 
					  currency: string;
 | 
				
			||||||
@ -65,13 +67,15 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
				
			|||||||
  private lastReservesBlockUpdate: number = 0;
 | 
					  private lastReservesBlockUpdate: number = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  goggleResolution = 82;
 | 
					  goggleResolution = 82;
 | 
				
			||||||
  goggleCycle = [
 | 
					  goggleCycle: { index: number, name: string, mode: FilterMode, filters: string[] }[] = [
 | 
				
			||||||
    { index: 0, name: 'All' },
 | 
					    { index: 0, name: 'All', mode: 'and', filters: [] },
 | 
				
			||||||
    { index: 1, name: 'Consolidations', flag: 0b00000010_00000000_00000000_00000000_00000000n },
 | 
					    { index: 1, name: 'Consolidation', mode: 'and', filters: ['consolidation'] },
 | 
				
			||||||
    { index: 2, name: 'Coinjoin', flag: 0b00000001_00000000_00000000_00000000_00000000n },
 | 
					    { index: 2, name: 'Coinjoin', mode: 'and', filters: ['coinjoin'] },
 | 
				
			||||||
    { index: 3, name: '💩', flag: 0b00000100_00000000_00000000_00000000n | 0b00000010_00000000_00000000_00000000n | 0b00000001_00000000_00000000_00000000n },
 | 
					    { index: 3, name: '💩', mode: 'or', filters: ['inscription', 'fake_pubkey', 'op_return'] },
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
  goggleIndex = 0; // Math.floor(Math.random() * this.goggleCycle.length);
 | 
					  goggleFlags = 0n;
 | 
				
			||||||
 | 
					  goggleMode: FilterMode = 'and';
 | 
				
			||||||
 | 
					  goggleIndex = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private destroy$ = new Subject();
 | 
					  private destroy$ = new Subject();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -87,6 +91,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngOnDestroy(): void {
 | 
					  ngOnDestroy(): void {
 | 
				
			||||||
 | 
					    this.filterSubscription.unsubscribe();
 | 
				
			||||||
    this.mempoolInfoSubscription.unsubscribe();
 | 
					    this.mempoolInfoSubscription.unsubscribe();
 | 
				
			||||||
    this.currencySubscription.unsubscribe();
 | 
					    this.currencySubscription.unsubscribe();
 | 
				
			||||||
    this.websocketService.stopTrackRbfSummary();
 | 
					    this.websocketService.stopTrackRbfSummary();
 | 
				
			||||||
@ -107,6 +112,30 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
				
			|||||||
        map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100)
 | 
					        map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100)
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.filterSubscription = this.stateService.activeGoggles$.subscribe((active: ActiveFilter) => {
 | 
				
			||||||
 | 
					      const activeFilters = active.filters.sort().join(',');
 | 
				
			||||||
 | 
					      for (const goggle of this.goggleCycle) {
 | 
				
			||||||
 | 
					        if (goggle.mode === active.mode) {
 | 
				
			||||||
 | 
					          const goggleFilters = goggle.filters.sort().join(',');
 | 
				
			||||||
 | 
					          if (goggleFilters === activeFilters) {
 | 
				
			||||||
 | 
					            this.goggleIndex = goggle.index;
 | 
				
			||||||
 | 
					            this.goggleFlags = toFlags(goggle.filters);
 | 
				
			||||||
 | 
					            this.goggleMode = goggle.mode;
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.goggleCycle.push({
 | 
				
			||||||
 | 
					        index: this.goggleCycle.length,
 | 
				
			||||||
 | 
					        name: 'Custom',
 | 
				
			||||||
 | 
					        mode: active.mode,
 | 
				
			||||||
 | 
					        filters: active.filters,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      this.goggleIndex = this.goggleCycle.length - 1;
 | 
				
			||||||
 | 
					      this.goggleFlags = toFlags(active.filters);
 | 
				
			||||||
 | 
					      this.goggleMode = active.mode;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.mempoolInfoData$ = combineLatest([
 | 
					    this.mempoolInfoData$ = combineLatest([
 | 
				
			||||||
      this.stateService.mempoolInfo$,
 | 
					      this.stateService.mempoolInfo$,
 | 
				
			||||||
      this.stateService.vbytesPerSecond$
 | 
					      this.stateService.vbytesPerSecond$
 | 
				
			||||||
@ -375,6 +404,11 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
				
			|||||||
    return Array.from({ length: num }, (_, i) => i + 1);
 | 
					    return Array.from({ length: num }, (_, i) => i + 1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  setFilter(index): void {
 | 
				
			||||||
 | 
					    const selected = this.goggleCycle[index];
 | 
				
			||||||
 | 
					    this.stateService.activeGoggles$.next(selected);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @HostListener('window:resize', ['$event'])
 | 
					  @HostListener('window:resize', ['$event'])
 | 
				
			||||||
  onResize(): void {
 | 
					  onResize(): void {
 | 
				
			||||||
    if (window.innerWidth >= 992) {
 | 
					    if (window.innerWidth >= 992) {
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ import { filter, map, scan, shareReplay } from 'rxjs/operators';
 | 
				
			|||||||
import { StorageService } from './storage.service';
 | 
					import { StorageService } from './storage.service';
 | 
				
			||||||
import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils';
 | 
					import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils';
 | 
				
			||||||
import { ApiService } from './api.service';
 | 
					import { ApiService } from './api.service';
 | 
				
			||||||
 | 
					import { ActiveFilter } from '../shared/filters.utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface MarkBlockState {
 | 
					export interface MarkBlockState {
 | 
				
			||||||
  blockHeight?: number;
 | 
					  blockHeight?: number;
 | 
				
			||||||
@ -150,7 +151,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<string[]> = new BehaviorSubject([]);
 | 
					  activeGoggles$: BehaviorSubject<ActiveFilter> = new BehaviorSubject({ mode: 'and', filters: [] });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @Inject(PLATFORM_ID) private platformId: any,
 | 
					    @Inject(PLATFORM_ID) private platformId: any,
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,13 @@ export interface Filter {
 | 
				
			|||||||
  important?: boolean,
 | 
					  important?: boolean,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type FilterMode = 'and' | 'or';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ActiveFilter {
 | 
				
			||||||
 | 
					  mode: FilterMode,
 | 
				
			||||||
 | 
					  filters: string[],
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// binary flags for transaction classification
 | 
					// binary flags for transaction classification
 | 
				
			||||||
export const TransactionFlags = {
 | 
					export const TransactionFlags = {
 | 
				
			||||||
  // features
 | 
					  // features
 | 
				
			||||||
@ -43,6 +50,14 @@ export const TransactionFlags = {
 | 
				
			|||||||
  sighash_acp:    0b00010000_00000000_00000000_00000000_00000000_00000000n,
 | 
					  sighash_acp:    0b00010000_00000000_00000000_00000000_00000000_00000000n,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function toFlags(filters: string[]): bigint {
 | 
				
			||||||
 | 
					  let flag = 0n;
 | 
				
			||||||
 | 
					  for (const filter of filters) {
 | 
				
			||||||
 | 
					    flag |= TransactionFlags[filter];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return flag;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const TransactionFilters: { [key: string]: Filter } = {
 | 
					export const TransactionFilters: { [key: string]: Filter } = {
 | 
				
			||||||
    /* features */
 | 
					    /* features */
 | 
				
			||||||
    rbf: { key: 'rbf', label: 'RBF enabled', flag: TransactionFlags.rbf, toggle: 'rbf', important: true },
 | 
					    rbf: { key: 'rbf', label: 'RBF enabled', flag: TransactionFlags.rbf, toggle: 'rbf', important: true },
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user