Merge pull request #3951 from mempool/mononaut/tx-highlight
Highlight searched transactions in the block visualizations
This commit is contained in:
		
						commit
						f15f0570d4
					
				| @ -6,6 +6,8 @@ import TxSprite from './tx-sprite'; | |||||||
| import TxView from './tx-view'; | import TxView from './tx-view'; | ||||||
| import { Position } from './sprite-types'; | import { Position } from './sprite-types'; | ||||||
| import { Price } from '../../services/price.service'; | import { Price } from '../../services/price.service'; | ||||||
|  | import { StateService } from '../../services/state.service'; | ||||||
|  | import { Subscription } from 'rxjs'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-block-overview-graph', |   selector: 'app-block-overview-graph', | ||||||
| @ -44,16 +46,25 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | |||||||
|   scene: BlockScene; |   scene: BlockScene; | ||||||
|   hoverTx: TxView | void; |   hoverTx: TxView | void; | ||||||
|   selectedTx: TxView | void; |   selectedTx: TxView | void; | ||||||
|  |   highlightTx: TxView | void; | ||||||
|   mirrorTx: TxView | void; |   mirrorTx: TxView | void; | ||||||
|   tooltipPosition: Position; |   tooltipPosition: Position; | ||||||
| 
 | 
 | ||||||
|   readyNextFrame = false; |   readyNextFrame = false; | ||||||
| 
 | 
 | ||||||
|  |   searchText: string; | ||||||
|  |   searchSubscription: Subscription; | ||||||
|  | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     readonly ngZone: NgZone, |     readonly ngZone: NgZone, | ||||||
|     readonly elRef: ElementRef, |     readonly elRef: ElementRef, | ||||||
|  |     private stateService: StateService, | ||||||
|   ) { |   ) { | ||||||
|     this.vertexArray = new FastVertexArray(512, TxSprite.dataSize); |     this.vertexArray = new FastVertexArray(512, TxSprite.dataSize); | ||||||
|  |     this.searchSubscription = this.stateService.searchText$.subscribe((text) => { | ||||||
|  |       this.searchText = text; | ||||||
|  |       this.updateSearchHighlight(); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngAfterViewInit(): void { |   ngAfterViewInit(): void { | ||||||
| @ -109,6 +120,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | |||||||
|       this.scene.setup(transactions); |       this.scene.setup(transactions); | ||||||
|       this.readyNextFrame = true; |       this.readyNextFrame = true; | ||||||
|       this.start(); |       this.start(); | ||||||
|  |       this.updateSearchHighlight(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -116,6 +128,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | |||||||
|     if (this.scene) { |     if (this.scene) { | ||||||
|       this.scene.enter(transactions, direction); |       this.scene.enter(transactions, direction); | ||||||
|       this.start(); |       this.start(); | ||||||
|  |       this.updateSearchHighlight(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -123,6 +136,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | |||||||
|     if (this.scene) { |     if (this.scene) { | ||||||
|       this.scene.exit(direction); |       this.scene.exit(direction); | ||||||
|       this.start(); |       this.start(); | ||||||
|  |       this.updateSearchHighlight(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -130,6 +144,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | |||||||
|     if (this.scene) { |     if (this.scene) { | ||||||
|       this.scene.replace(transactions || [], direction, sort); |       this.scene.replace(transactions || [], direction, sort); | ||||||
|       this.start(); |       this.start(); | ||||||
|  |       this.updateSearchHighlight(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -137,6 +152,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | |||||||
|     if (this.scene) { |     if (this.scene) { | ||||||
|       this.scene.update(add, remove, change, direction, resetLayout); |       this.scene.update(add, remove, change, direction, resetLayout); | ||||||
|       this.start(); |       this.start(); | ||||||
|  |       this.updateSearchHighlight(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -406,6 +422,19 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   updateSearchHighlight(): void { | ||||||
|  |     if (this.highlightTx && this.highlightTx.txid !== this.searchText && this.scene) { | ||||||
|  |       this.scene.setHighlight(this.highlightTx, false); | ||||||
|  |       this.start(); | ||||||
|  |     } else if (this.scene?.txs && this.searchText && this.searchText.length === 64) { | ||||||
|  |       this.highlightTx = this.scene.txs[this.searchText]; | ||||||
|  |       if (this.highlightTx) { | ||||||
|  |         this.scene.setHighlight(this.highlightTx, true); | ||||||
|  |         this.start(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   setHighlightingEnabled(enabled: boolean): void { |   setHighlightingEnabled(enabled: boolean): void { | ||||||
|     if (this.scene) { |     if (this.scene) { | ||||||
|       this.scene.setHighlighting(enabled); |       this.scene.setHighlighting(enabled); | ||||||
|  | |||||||
| @ -215,6 +215,10 @@ export default class BlockScene { | |||||||
|     this.animateUntil = Math.max(this.animateUntil, tx.setHover(value)); |     this.animateUntil = Math.max(this.animateUntil, tx.setHover(value)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   setHighlight(tx: TxView, value: boolean): void { | ||||||
|  |     this.animateUntil = Math.max(this.animateUntil, tx.setHighlight(value)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   private init({ width, height, resolution, blockLimit, orientation, flip, vertexArray, highlighting, pixelAlign }: |   private init({ width, height, resolution, blockLimit, orientation, flip, vertexArray, highlighting, pixelAlign }: | ||||||
|       { width: number, height: number, resolution: number, blockLimit: number, |       { width: number, height: number, resolution: number, blockLimit: number, | ||||||
|         orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean, pixelAlign: boolean } |         orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean, pixelAlign: boolean } | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ import BlockScene from './block-scene'; | |||||||
| 
 | 
 | ||||||
| const hoverTransitionTime = 300; | const hoverTransitionTime = 300; | ||||||
| const defaultHoverColor = hexToColor('1bd8f4'); | const defaultHoverColor = hexToColor('1bd8f4'); | ||||||
|  | const defaultHighlightColor = hexToColor('800080'); | ||||||
| 
 | 
 | ||||||
| const feeColors = mempoolFeeColors.map(hexToColor); | const feeColors = mempoolFeeColors.map(hexToColor); | ||||||
| const auditFeeColors = feeColors.map((color) => darken(desaturate(color, 0.3), 0.9)); | const auditFeeColors = feeColors.map((color) => darken(desaturate(color, 0.3), 0.9)); | ||||||
| @ -44,8 +45,10 @@ export default class TxView implements TransactionStripped { | |||||||
|   initialised: boolean; |   initialised: boolean; | ||||||
|   vertexArray: FastVertexArray; |   vertexArray: FastVertexArray; | ||||||
|   hover: boolean; |   hover: boolean; | ||||||
|  |   highlight: boolean; | ||||||
|   sprite: TxSprite; |   sprite: TxSprite; | ||||||
|   hoverColor: Color | void; |   hoverColor: Color | void; | ||||||
|  |   highlightColor: Color | void; | ||||||
| 
 | 
 | ||||||
|   screenPosition: Square; |   screenPosition: Square; | ||||||
|   gridPosition: Square | void; |   gridPosition: Square | void; | ||||||
| @ -150,10 +153,42 @@ export default class TxView implements TransactionStripped { | |||||||
|     } else { |     } else { | ||||||
|       this.hover = false; |       this.hover = false; | ||||||
|       this.hoverColor = null; |       this.hoverColor = null; | ||||||
|  |       if (this.highlight) { | ||||||
|  |         this.setHighlight(true, this.highlightColor); | ||||||
|  |       } else { | ||||||
|         if (this.sprite) { |         if (this.sprite) { | ||||||
|           this.sprite.resume(hoverTransitionTime); |           this.sprite.resume(hoverTransitionTime); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |     } | ||||||
|  |     this.dirty = false; | ||||||
|  |     return performance.now() + hoverTransitionTime; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Temporarily override the tx color
 | ||||||
|  |   // returns minimum transition end time
 | ||||||
|  |   setHighlight(highlightOn: boolean, color: Color | void = defaultHighlightColor): number { | ||||||
|  |     if (highlightOn) { | ||||||
|  |       this.highlight = true; | ||||||
|  |       this.highlightColor = color; | ||||||
|  | 
 | ||||||
|  |       this.sprite.update({ | ||||||
|  |         ...this.highlightColor, | ||||||
|  |         duration: hoverTransitionTime, | ||||||
|  |         adjust: false, | ||||||
|  |         temp: true | ||||||
|  |       }); | ||||||
|  |     } else { | ||||||
|  |       this.highlight = false; | ||||||
|  |       this.highlightColor = null; | ||||||
|  |       if (this.hover) { | ||||||
|  |         this.setHover(true, this.hoverColor); | ||||||
|  |       } else { | ||||||
|  |         if (this.sprite) { | ||||||
|  |           this.sprite.resume(hoverTransitionTime); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|     this.dirty = false; |     this.dirty = false; | ||||||
|     return performance.now() + hoverTransitionTime; |     return performance.now() + hoverTransitionTime; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -80,6 +80,9 @@ export class SearchFormComponent implements OnInit { | |||||||
|         } |         } | ||||||
|         return text.trim(); |         return text.trim(); | ||||||
|       }), |       }), | ||||||
|  |       tap((text) => { | ||||||
|  |         this.stateService.searchText$.next(text); | ||||||
|  |       }), | ||||||
|       distinctUntilChanged(), |       distinctUntilChanged(), | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -129,6 +129,7 @@ export class StateService { | |||||||
| 
 | 
 | ||||||
|   markBlock$ = new BehaviorSubject<MarkBlockState>({}); |   markBlock$ = new BehaviorSubject<MarkBlockState>({}); | ||||||
|   keyNavigation$ = new Subject<KeyboardEvent>(); |   keyNavigation$ = new Subject<KeyboardEvent>(); | ||||||
|  |   searchText$ = new BehaviorSubject<string>(''); | ||||||
| 
 | 
 | ||||||
|   blockScrolling$: Subject<boolean> = new Subject<boolean>(); |   blockScrolling$: Subject<boolean> = new Subject<boolean>(); | ||||||
|   resetScroll$: Subject<boolean> = new Subject<boolean>(); |   resetScroll$: Subject<boolean> = new Subject<boolean>(); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user