add mouseover tooltips to rbf timelines
This commit is contained in:
		
							parent
							
								
									086b41d958
								
							
						
					
					
						commit
						723212c918
					
				@ -0,0 +1,30 @@
 | 
			
		||||
<div
 | 
			
		||||
  #tooltip
 | 
			
		||||
  *ngIf="rbfInfo"
 | 
			
		||||
  class="rbf-tooltip"
 | 
			
		||||
  [style.left]="tooltipPosition.x + 'px'"
 | 
			
		||||
  [style.top]="tooltipPosition.y + 'px'"
 | 
			
		||||
>
 | 
			
		||||
  <span class="txid">{{ rbfInfo.tx.txid | shortenString : 24 }}</span>
 | 
			
		||||
  <table class="table table-borderless table-striped">
 | 
			
		||||
    <tbody>
 | 
			
		||||
      <tr>
 | 
			
		||||
        <td i18n="transaction.first-seen|Transaction first seen">First seen</td>
 | 
			
		||||
        <td><i><app-time kind="since" [time]="rbfInfo.time" [fastRender]="true"></app-time></i></td>
 | 
			
		||||
      </tr>
 | 
			
		||||
      <tr>
 | 
			
		||||
        <td i18n="transaction.fee|Transaction fee">Fee</td>
 | 
			
		||||
        <td>{{ rbfInfo.tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></td>
 | 
			
		||||
      </tr>
 | 
			
		||||
      <tr>
 | 
			
		||||
        <td i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
 | 
			
		||||
        <td [innerHTML]="'‎' + (rbfInfo.tx.vsize | vbytes: 2)"></td>
 | 
			
		||||
      </tr>
 | 
			
		||||
      <tr>
 | 
			
		||||
        <span *ngIf="rbfInfo.tx.rbf; else rbfDisabled" class="badge badge-success" i18n="rbfInfo-features.tag.rbf|RBF">RBF</span>
 | 
			
		||||
        <ng-template #rbfDisabled><span class="badge badge-danger mr-1"><del i18n="rbfInfo-features.tag.rbf|RBF">RBF</del></span></ng-template>
 | 
			
		||||
        <span *ngIf="rbfInfo.tx.mined" class="badge badge-success" i18n="transaction.rbf.mined">Mined</span>        
 | 
			
		||||
      </tr>
 | 
			
		||||
    </tbody>
 | 
			
		||||
  </table>
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,32 @@
 | 
			
		||||
.rbf-tooltip {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  z-index: 3;
 | 
			
		||||
  background: rgba(#11131f, 0.95);
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
 | 
			
		||||
  color: #b1b1b1;
 | 
			
		||||
  padding: 10px 15px;
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  pointer-events: none;
 | 
			
		||||
  max-width: 300px;
 | 
			
		||||
 | 
			
		||||
  p {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  table tr td {
 | 
			
		||||
    padding: 0;
 | 
			
		||||
 | 
			
		||||
    &:last-child {
 | 
			
		||||
      text-align: right;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .badge {
 | 
			
		||||
    margin-right: 1em;
 | 
			
		||||
    &:last-child {
 | 
			
		||||
      margin-right: 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,35 @@
 | 
			
		||||
import { Component, ElementRef, ViewChild, Input, OnChanges } from '@angular/core';
 | 
			
		||||
import { RbfInfo } from '../../interfaces/node-api.interface';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-rbf-timeline-tooltip',
 | 
			
		||||
  templateUrl: './rbf-timeline-tooltip.component.html',
 | 
			
		||||
  styleUrls: ['./rbf-timeline-tooltip.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class RbfTimelineTooltipComponent implements OnChanges {
 | 
			
		||||
  @Input() rbfInfo: RbfInfo | void;
 | 
			
		||||
  @Input() cursorPosition: { x: number, y: number };
 | 
			
		||||
 | 
			
		||||
  tooltipPosition = { x: 0, y: 0 };
 | 
			
		||||
 | 
			
		||||
  @ViewChild('tooltip') tooltipElement: ElementRef<HTMLCanvasElement>;
 | 
			
		||||
 | 
			
		||||
  constructor() {}
 | 
			
		||||
 | 
			
		||||
  ngOnChanges(changes): void {
 | 
			
		||||
    if (changes.cursorPosition && changes.cursorPosition.currentValue) {
 | 
			
		||||
      let x = Math.max(10, changes.cursorPosition.currentValue.x - 50);
 | 
			
		||||
      let y = changes.cursorPosition.currentValue.y + 20;
 | 
			
		||||
      if (this.tooltipElement) {
 | 
			
		||||
        const elementBounds = this.tooltipElement.nativeElement.getBoundingClientRect();
 | 
			
		||||
        if ((x + elementBounds.width) > (window.innerWidth - 10)) {
 | 
			
		||||
          x = Math.max(0, window.innerWidth - elementBounds.width - 10);
 | 
			
		||||
        }
 | 
			
		||||
        if (y + elementBounds.height > (window.innerHeight - 20)) {
 | 
			
		||||
          y = y - elementBounds.height - 20;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      this.tooltipPosition = { x, y };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -16,9 +16,18 @@
 | 
			
		||||
      <div class="nodes">
 | 
			
		||||
        <ng-container *ngFor="let cell of timeline; let i = index;">
 | 
			
		||||
          <ng-container *ngIf="cell.replacement; else nonNode">
 | 
			
		||||
            <div class="node" [class.selected]="txid === cell.replacement.tx.txid" [class.mined]="cell.replacement.tx.mined" [class.first-node]="cell.first">
 | 
			
		||||
            <div class="node"
 | 
			
		||||
              [class.selected]="txid === cell.replacement.tx.txid"
 | 
			
		||||
              [class.mined]="cell.replacement.tx.mined"
 | 
			
		||||
              [class.first-node]="cell.first"
 | 
			
		||||
            >
 | 
			
		||||
              <div class="track"></div>
 | 
			
		||||
              <a class="shape-border" [class.rbf]="cell.replacement.tx.rbf" [routerLink]="['/tx/' | relativeUrl, cell.replacement.tx.txid]" [title]="cell.replacement.tx.txid">
 | 
			
		||||
              <a class="shape-border"
 | 
			
		||||
                [class.rbf]="cell.replacement.tx.rbf"
 | 
			
		||||
                [routerLink]="['/tx/' | relativeUrl, cell.replacement.tx.txid]"
 | 
			
		||||
                (pointerover)="onHover($event, cell.replacement);"
 | 
			
		||||
                (pointerout)="onBlur($event);"
 | 
			
		||||
              >
 | 
			
		||||
                <div class="shape"></div>
 | 
			
		||||
              </a>
 | 
			
		||||
              <span class="fee-rate">{{ cell.replacement.tx.fee / (cell.replacement.tx.vsize) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></span>
 | 
			
		||||
@ -49,6 +58,11 @@
 | 
			
		||||
    <div class="interval-spacer"></div>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
 | 
			
		||||
  <app-rbf-timeline-tooltip
 | 
			
		||||
    [rbfInfo]="hoverInfo"
 | 
			
		||||
    [cursorPosition]="tooltipPosition"
 | 
			
		||||
  ></app-rbf-timeline-tooltip>
 | 
			
		||||
 | 
			
		||||
  <!-- <app-rbf-timeline-tooltip
 | 
			
		||||
    *ngIf=[tooltip]
 | 
			
		||||
    [line]="hoverLine"
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { Component, Input, OnInit, OnChanges, Inject, LOCALE_ID } from '@angular/core';
 | 
			
		||||
import { Component, Input, OnInit, OnChanges, Inject, LOCALE_ID, HostListener } from '@angular/core';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
import { RbfInfo, RbfTree } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
@ -22,6 +22,9 @@ export class RbfTimelineComponent implements OnInit, OnChanges {
 | 
			
		||||
  @Input() txid: string;
 | 
			
		||||
  rows: TimelineCell[][] = [];
 | 
			
		||||
 | 
			
		||||
  hoverInfo: RbfInfo | void = null;
 | 
			
		||||
  tooltipPosition = { x: 0, y: 0 };
 | 
			
		||||
 | 
			
		||||
  dir: 'rtl' | 'ltr' = 'ltr';
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
@ -120,46 +123,59 @@ export class RbfTimelineComponent implements OnInit, OnChanges {
 | 
			
		||||
    return rows;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    // annotates a 2D timeline array with info needed to draw connecting lines for multi-replacements
 | 
			
		||||
    connectTimelines(timelines: RbfInfo[][]): TimelineCell[][] {
 | 
			
		||||
      const rows: TimelineCell[][] = [];
 | 
			
		||||
      timelines.forEach((lines, row) => {
 | 
			
		||||
        rows.push([]);
 | 
			
		||||
        let started = false;
 | 
			
		||||
        let finished = false;
 | 
			
		||||
        lines.forEach((replacement, column) => {
 | 
			
		||||
          const cell: TimelineCell = {};
 | 
			
		||||
          if (replacement) {
 | 
			
		||||
            cell.replacement = replacement;
 | 
			
		||||
  // annotates a 2D timeline array with info needed to draw connecting lines for multi-replacements
 | 
			
		||||
  connectTimelines(timelines: RbfInfo[][]): TimelineCell[][] {
 | 
			
		||||
    const rows: TimelineCell[][] = [];
 | 
			
		||||
    timelines.forEach((lines, row) => {
 | 
			
		||||
      rows.push([]);
 | 
			
		||||
      let started = false;
 | 
			
		||||
      let finished = false;
 | 
			
		||||
      lines.forEach((replacement, column) => {
 | 
			
		||||
        const cell: TimelineCell = {};
 | 
			
		||||
        if (replacement) {
 | 
			
		||||
          cell.replacement = replacement;
 | 
			
		||||
        }
 | 
			
		||||
        rows[row].push(cell);
 | 
			
		||||
        if (replacement) {
 | 
			
		||||
          if (!started) {
 | 
			
		||||
            cell.first = true;
 | 
			
		||||
            started = true;
 | 
			
		||||
          }
 | 
			
		||||
          rows[row].push(cell);
 | 
			
		||||
          if (replacement) {
 | 
			
		||||
            if (!started) {
 | 
			
		||||
              cell.first = true;
 | 
			
		||||
              started = true;
 | 
			
		||||
            }
 | 
			
		||||
          } else if (started && !finished) {
 | 
			
		||||
            if (column < timelines[row].length) {
 | 
			
		||||
              let matched = false;
 | 
			
		||||
              for (let i = row; i >= 0 && !matched; i--) {
 | 
			
		||||
                const nextCell = rows[i][column];
 | 
			
		||||
                if (nextCell.replacement) {
 | 
			
		||||
                  matched = true;
 | 
			
		||||
                } else if (i === row) {
 | 
			
		||||
                  rows[i][column] = {
 | 
			
		||||
                    connector: 'corner'
 | 
			
		||||
                  };
 | 
			
		||||
                } else if (nextCell.connector !== 'corner') {
 | 
			
		||||
                  rows[i][column] = {
 | 
			
		||||
                    connector: 'pipe'
 | 
			
		||||
                  };
 | 
			
		||||
                }
 | 
			
		||||
        } else if (started && !finished) {
 | 
			
		||||
          if (column < timelines[row].length) {
 | 
			
		||||
            let matched = false;
 | 
			
		||||
            for (let i = row; i >= 0 && !matched; i--) {
 | 
			
		||||
              const nextCell = rows[i][column];
 | 
			
		||||
              if (nextCell.replacement) {
 | 
			
		||||
                matched = true;
 | 
			
		||||
              } else if (i === row) {
 | 
			
		||||
                rows[i][column] = {
 | 
			
		||||
                  connector: 'corner'
 | 
			
		||||
                };
 | 
			
		||||
              } else if (nextCell.connector !== 'corner') {
 | 
			
		||||
                rows[i][column] = {
 | 
			
		||||
                  connector: 'pipe'
 | 
			
		||||
                };
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            finished = true;
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
          finished = true;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      return rows;
 | 
			
		||||
    }
 | 
			
		||||
    });
 | 
			
		||||
    return rows;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @HostListener('pointermove', ['$event'])
 | 
			
		||||
  onPointerMove(event) {
 | 
			
		||||
    this.tooltipPosition = { x: event.clientX, y: event.clientY };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onHover(event, replacement): void {
 | 
			
		||||
    this.hoverInfo = replacement;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onBlur(event): void {
 | 
			
		||||
    this.hoverInfo = null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -62,6 +62,7 @@ import { DifficultyTooltipComponent } from '../components/difficulty/difficulty-
 | 
			
		||||
import { DifficultyMiningComponent } from '../components/difficulty-mining/difficulty-mining.component';
 | 
			
		||||
import { TermsOfServiceComponent } from '../components/terms-of-service/terms-of-service.component';
 | 
			
		||||
import { RbfTimelineComponent } from '../components/rbf-timeline/rbf-timeline.component';
 | 
			
		||||
import { RbfTimelineTooltipComponent } from '../components/rbf-timeline/rbf-timeline-tooltip.component';
 | 
			
		||||
import { TxBowtieGraphComponent } from '../components/tx-bowtie-graph/tx-bowtie-graph.component';
 | 
			
		||||
import { TxBowtieGraphTooltipComponent } from '../components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component';
 | 
			
		||||
import { PrivacyPolicyComponent } from '../components/privacy-policy/privacy-policy.component';
 | 
			
		||||
@ -141,6 +142,7 @@ import { TestnetAlertComponent } from './components/testnet-alert/testnet-alert.
 | 
			
		||||
    DifficultyMiningComponent,
 | 
			
		||||
    DifficultyTooltipComponent,
 | 
			
		||||
    RbfTimelineComponent,
 | 
			
		||||
    RbfTimelineTooltipComponent,
 | 
			
		||||
    TxBowtieGraphComponent,
 | 
			
		||||
    TxBowtieGraphTooltipComponent,
 | 
			
		||||
    TermsOfServiceComponent,
 | 
			
		||||
@ -247,6 +249,7 @@ import { TestnetAlertComponent } from './components/testnet-alert/testnet-alert.
 | 
			
		||||
    DifficultyMiningComponent,
 | 
			
		||||
    DifficultyTooltipComponent,
 | 
			
		||||
    RbfTimelineComponent,
 | 
			
		||||
    RbfTimelineTooltipComponent,
 | 
			
		||||
    TxBowtieGraphComponent,
 | 
			
		||||
    TxBowtieGraphTooltipComponent,
 | 
			
		||||
    TermsOfServiceComponent,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user