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">
 | 
			
		||||
    <span class="badge badge-pill badge-warning beta" i18n="beta">beta</span>
 | 
			
		||||
    <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" size="lg"></fa-icon>
 | 
			
		||||
@ -14,6 +14,15 @@
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <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;">
 | 
			
		||||
      <h5>{{ group.label }}</h5>
 | 
			
		||||
      <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 {
 | 
			
		||||
    .menu-toggle {
 | 
			
		||||
      opacity: 0.5;
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
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 { Subscription } from 'rxjs';
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ import { Subscription } from 'rxjs';
 | 
			
		||||
export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
  @Input() cssWidth: number = 800;
 | 
			
		||||
  @Input() excludeFilters: string[] = [];
 | 
			
		||||
  @Output() onFilterChanged: EventEmitter<bigint | null> = new EventEmitter();
 | 
			
		||||
  @Output() onFilterChanged: EventEmitter<ActiveFilter | null> = new EventEmitter();
 | 
			
		||||
 | 
			
		||||
  filterSubscription: Subscription;
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
  disabledFilters: { [key: string]: boolean } = {};
 | 
			
		||||
  activeFilters: string[] = [];
 | 
			
		||||
  filterFlags: { [key: string]: boolean } = {};
 | 
			
		||||
  filterMode: FilterMode = 'and';
 | 
			
		||||
  menuOpen: boolean = false;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
@ -29,15 +30,16 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  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)) {
 | 
			
		||||
        this.filterFlags[key] = false;
 | 
			
		||||
      }
 | 
			
		||||
      for (const key of activeFilters) {
 | 
			
		||||
      for (const key of active.filters) {
 | 
			
		||||
        this.filterFlags[key] = !this.disabledFilters[key];
 | 
			
		||||
      }
 | 
			
		||||
      this.activeFilters = [...activeFilters.filter(key => !this.disabledFilters[key])];
 | 
			
		||||
      this.onFilterChanged.emit(this.getBooleanFlags());
 | 
			
		||||
      this.activeFilters = [...active.filters.filter(key => !this.disabledFilters[key])];
 | 
			
		||||
      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 {
 | 
			
		||||
    const filter = this.filters[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);
 | 
			
		||||
    }
 | 
			
		||||
    const booleanFlags = this.getBooleanFlags();
 | 
			
		||||
    this.onFilterChanged.emit(booleanFlags);
 | 
			
		||||
    this.stateService.activeGoggles$.next([...this.activeFilters]);
 | 
			
		||||
    this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters });
 | 
			
		||||
    this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] });
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  getBooleanFlags(): bigint | null {
 | 
			
		||||
@ -90,7 +98,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
  @HostListener('document:click', ['$event'])
 | 
			
		||||
  onClick(event): boolean {
 | 
			
		||||
    // click away from menu
 | 
			
		||||
    if (!event.target.closest('button')) {
 | 
			
		||||
    if (!event.target.closest('button') && !event.target.closest('label')) {
 | 
			
		||||
      this.menuOpen = false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import { Price } from '../../services/price.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { Subscription } from 'rxjs';
 | 
			
		||||
import { defaultColorFunction, setOpacity, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils';
 | 
			
		||||
import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils';
 | 
			
		||||
 | 
			
		||||
const unmatchedOpacity = 0.2;
 | 
			
		||||
const unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity));
 | 
			
		||||
@ -42,7 +43,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
 | 
			
		||||
  @Input() showFilters: boolean = false;
 | 
			
		||||
  @Input() excludeFilters: string[] = [];
 | 
			
		||||
  @Input() filterFlags: bigint | null = null;
 | 
			
		||||
  @Input() filterMode: 'and' | 'or' = 'and';
 | 
			
		||||
  @Input() filterMode: FilterMode = 'and';
 | 
			
		||||
  @Input() blockConversion: Price;
 | 
			
		||||
  @Input() overrideColors: ((tx: TxView) => Color) | null = null;
 | 
			
		||||
  @Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>();
 | 
			
		||||
@ -119,10 +120,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setFilterFlags(flags?: bigint | null): void {
 | 
			
		||||
    this.activeFilterFlags = this.filterFlags || flags || null;
 | 
			
		||||
  setFilterFlags(goggle?: ActiveFilter): void {
 | 
			
		||||
    this.filterMode = goggle?.mode || this.filterMode;
 | 
			
		||||
    this.activeFilterFlags = goggle?.filters ? toFlags(goggle.filters) : this.filterFlags;
 | 
			
		||||
    if (this.scene) {
 | 
			
		||||
      if (this.activeFilterFlags != null) {
 | 
			
		||||
      if (this.activeFilterFlags != null && this.filtersAvailable) {
 | 
			
		||||
        this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags));
 | 
			
		||||
      } else {
 | 
			
		||||
        this.scene.setColorFunction(this.overrideColors);
 | 
			
		||||
@ -157,7 +159,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
 | 
			
		||||
 | 
			
		||||
  // initialize the scene without any entry transition
 | 
			
		||||
  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) {
 | 
			
		||||
      this.scene.setup(transactions);
 | 
			
		||||
      this.readyNextFrame = true;
 | 
			
		||||
@ -523,8 +529,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) {
 | 
			
		||||
    console.log('getting filter color function: ', flags, this.filterMode);
 | 
			
		||||
    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);
 | 
			
		||||
      } else {
 | 
			
		||||
        return defaultColorFunction(
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pi
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
import { Color } from '../block-overview-graph/sprite-types';
 | 
			
		||||
import TxView from '../block-overview-graph/tx-view';
 | 
			
		||||
import { FilterMode } from '../../shared/filters.utils';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-mempool-block-overview',
 | 
			
		||||
@ -22,7 +23,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
 | 
			
		||||
  @Input() showFilters: boolean = false;
 | 
			
		||||
  @Input() overrideColors: ((tx: TxView) => Color) | null = null;
 | 
			
		||||
  @Input() filterFlags: bigint | undefined = undefined;
 | 
			
		||||
  @Input() filterMode: 'and' | 'or' = 'and';
 | 
			
		||||
  @Input() filterMode: FilterMode = 'and';
 | 
			
		||||
  @Output() txPreviewEvent = new EventEmitter<TransactionStripped | void>();
 | 
			
		||||
 | 
			
		||||
  @ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent;
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@
 | 
			
		||||
            <div class="quick-filter">
 | 
			
		||||
              <div class="btn-group btn-group-toggle">
 | 
			
		||||
                <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>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
@ -34,8 +34,8 @@
 | 
			
		||||
              <app-mempool-block-overview
 | 
			
		||||
                [index]="0"
 | 
			
		||||
                [resolution]="goggleResolution"
 | 
			
		||||
                [filterFlags]="goggleCycle[goggleIndex].flag"
 | 
			
		||||
                filterMode="or"
 | 
			
		||||
                [filterFlags]="goggleFlags"
 | 
			
		||||
                [filterMode]="goggleMode"
 | 
			
		||||
              ></app-mempool-block-overview>
 | 
			
		||||
            </div>
 | 
			
		||||
          </ng-template>
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ import { ApiService } from '../services/api.service';
 | 
			
		||||
import { StateService } from '../services/state.service';
 | 
			
		||||
import { WebsocketService } from '../services/websocket.service';
 | 
			
		||||
import { SeoService } from '../services/seo.service';
 | 
			
		||||
import { ActiveFilter, FilterMode, toFlags } from '../shared/filters.utils';
 | 
			
		||||
 | 
			
		||||
interface MempoolBlocksData {
 | 
			
		||||
  blocks: number;
 | 
			
		||||
@ -55,6 +56,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
			
		||||
  currentReserves$: Observable<CurrentPegs>;
 | 
			
		||||
  fullHistory$: Observable<any>;
 | 
			
		||||
  isLoad: boolean = true;
 | 
			
		||||
  filterSubscription: Subscription;
 | 
			
		||||
  mempoolInfoSubscription: Subscription;
 | 
			
		||||
  currencySubscription: Subscription;
 | 
			
		||||
  currency: string;
 | 
			
		||||
@ -65,13 +67,15 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
			
		||||
  private lastReservesBlockUpdate: number = 0;
 | 
			
		||||
 | 
			
		||||
  goggleResolution = 82;
 | 
			
		||||
  goggleCycle = [
 | 
			
		||||
    { index: 0, name: 'All' },
 | 
			
		||||
    { index: 1, name: 'Consolidations', flag: 0b00000010_00000000_00000000_00000000_00000000n },
 | 
			
		||||
    { index: 2, name: 'Coinjoin', flag: 0b00000001_00000000_00000000_00000000_00000000n },
 | 
			
		||||
    { index: 3, name: '💩', flag: 0b00000100_00000000_00000000_00000000n | 0b00000010_00000000_00000000_00000000n | 0b00000001_00000000_00000000_00000000n },
 | 
			
		||||
  goggleCycle: { index: number, name: string, mode: FilterMode, filters: string[] }[] = [
 | 
			
		||||
    { index: 0, name: 'All', mode: 'and', filters: [] },
 | 
			
		||||
    { index: 1, name: 'Consolidation', mode: 'and', filters: ['consolidation'] },
 | 
			
		||||
    { index: 2, name: 'Coinjoin', mode: 'and', filters: ['coinjoin'] },
 | 
			
		||||
    { 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();
 | 
			
		||||
 | 
			
		||||
@ -87,6 +91,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy(): void {
 | 
			
		||||
    this.filterSubscription.unsubscribe();
 | 
			
		||||
    this.mempoolInfoSubscription.unsubscribe();
 | 
			
		||||
    this.currencySubscription.unsubscribe();
 | 
			
		||||
    this.websocketService.stopTrackRbfSummary();
 | 
			
		||||
@ -107,6 +112,30 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
			
		||||
        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.stateService.mempoolInfo$,
 | 
			
		||||
      this.stateService.vbytesPerSecond$
 | 
			
		||||
@ -375,6 +404,11 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
			
		||||
    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'])
 | 
			
		||||
  onResize(): void {
 | 
			
		||||
    if (window.innerWidth >= 992) {
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import { filter, map, scan, shareReplay } from 'rxjs/operators';
 | 
			
		||||
import { StorageService } from './storage.service';
 | 
			
		||||
import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils';
 | 
			
		||||
import { ApiService } from './api.service';
 | 
			
		||||
import { ActiveFilter } from '../shared/filters.utils';
 | 
			
		||||
 | 
			
		||||
export interface MarkBlockState {
 | 
			
		||||
  blockHeight?: number;
 | 
			
		||||
@ -150,7 +151,7 @@ export class StateService {
 | 
			
		||||
  searchFocus$: Subject<boolean> = new Subject<boolean>();
 | 
			
		||||
  menuOpen$: BehaviorSubject<boolean> = new BehaviorSubject(false);
 | 
			
		||||
 | 
			
		||||
  activeGoggles$: BehaviorSubject<string[]> = new BehaviorSubject([]);
 | 
			
		||||
  activeGoggles$: BehaviorSubject<ActiveFilter> = new BehaviorSubject({ mode: 'and', filters: [] });
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    @Inject(PLATFORM_ID) private platformId: any,
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,13 @@ export interface Filter {
 | 
			
		||||
  important?: boolean,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type FilterMode = 'and' | 'or';
 | 
			
		||||
 | 
			
		||||
export interface ActiveFilter {
 | 
			
		||||
  mode: FilterMode,
 | 
			
		||||
  filters: string[],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// binary flags for transaction classification
 | 
			
		||||
export const TransactionFlags = {
 | 
			
		||||
  // features
 | 
			
		||||
@ -43,6 +50,14 @@ export const TransactionFlags = {
 | 
			
		||||
  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 } = {
 | 
			
		||||
    /* features */
 | 
			
		||||
    rbf: { key: 'rbf', label: 'RBF enabled', flag: TransactionFlags.rbf, toggle: 'rbf', important: true },
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user