import { Component , Input , ViewChild , SimpleChanges , ElementRef , OnChanges , AfterViewInit } from '@angular/core' ; import { Route } from '../../../backend/utils' ; import { UserActions } from '../../actions/user-actions/user-actions' ; import * as d3 from 'd3' ; import { Subscription , fromEvent } from 'rxjs' ; import { takeUntil , map , mergeMap , audit , tap } from 'rxjs/operators' ; @ Component ( { selector : 'bt-router-tree' , templateUrl : './router-tree.html' , styleUrls : [ './router-tree.css' ] } ) export class RouterTree implements OnChanges , AfterViewInit { @ ViewChild ( 'routeTree' , { static : true } ) private routeTreeComponent ; @ ViewChild ( 'resizer' , { static : true } ) private resizerElement ; @ ViewChild ( 'svgContainer' , { static : true } ) private svg : ElementRef ; @ ViewChild ( 'mainGroup' , { static : true } ) private g : ElementRef ; @ Input ( ) routerTree : Array < Route > ; selectedRoute : Route | any ; private tree : d3 . TreeLayout < { } > ; private sub : Subscription ; private routerTreeBaseHeight : number = 120 ; constructor ( private userActions , private element ) { } ngAfterViewInit ( ) { this . sub = this . _dragEvent ( ) . pipe ( tap ( delta => this . resizeElement ( delta + this . routerTreeBaseHeight ) ) , audit ( ( ) => fromEvent ( document , 'mouseup' ) ) ) . subscribe ( delta => { this . routerTreeBaseHeight += delta ; } ) ; } _dragEvent ( ) { return fromEvent ( this . resizerElement . nativeElement , 'mousedown' ) . pipe ( map ( ( e ) => e . clientY ) , mergeMap ( yPos => this . _mouseUpEvent ( yPos ) ) ) ; } _mouseUpEvent ( yPos ) { return fromEvent ( document , 'mousemove' ) . pipe ( takeUntil ( fromEvent ( document , 'mouseup' ) ) , map ( ( e ) => e . clientY - yPos ) ) ; } ngOnDestroy ( ) { if ( this . sub ) { this . sub . unsubscribe ( ) ; } } resizeElement ( mouseDelta ) { this . routeTreeComponent . nativeElement . style . height = `${ mouseDelta }px` ; } render ( ) { if ( ! this . routerTree ) { return ; } this . tree = d3 . tree ( ) ; const svg = d3 . select ( this . svg . nativeElement ) ; const g = d3 . select ( this . g . nativeElement ) ; const svgPadding = 20 ; this . tree . nodeSize ( [ 45 , 200 ] ) ; const root = { name : 'root' , children : this . routerTree , hash : null , path : null , specificity : null , handler : null , data : { } , isAux : false } ; const nodes = this . tree ( d3 . hierarchy ( root . children . length === 0 || root . children . length > 1 ? root : root . children [ 0 ] , d => d . children ) ) ; g . selectAll ( '.link' ) . data ( nodes . descendants ( ) . slice ( 1 ) ) . enter ( ) . append ( 'path' ) . attr ( 'class' , 'link' ) . attr ( 'd' , d => `
            M${ d . y },${ d . x }
            C${ ( d . y + d . parent . y ) / 2 },
              ${ d . x } ${ ( d . y + d . parent . y ) / 2 },
              ${ d . parent . x } ${ d . parent . y },
              ${ d . parent . x }` ) ; const node = g . selectAll ( 'g.node' ) . data ( nodes . descendants ( ) ) . enter ( ) . append ( 'g' ) . attr ( 'class' , 'node' ) . on ( 'mouseover' , n => this . onRollover ( n . data ) ) . on ( 'mouseout' , n => this . onRollover ( n . data ) ) . attr ( 'transform' , d => `translate(${ d . y },${ d . x })` ) ; node . append ( 'circle' ) . attr ( 'class' , d => ( ( < any > d . data ) . isAux ? 'node-aux-route' : 'node-route' ) ) . attr ( 'r' , 6 ) ; node . append ( 'text' ) . attr ( 'x' , d => ( d . children ? - 13 : 13 ) ) . attr ( 'dy' , '.35em' ) . attr ( 'text-anchor' , d => ( d . children ? 'end' : 'start' ) ) . text ( d => ( < any > d . data ) . name ) . attr ( 'class' , 'monospace' ) ; g . attr ( 'transform' , 'translate(0, 0)' ) ; const svgRect = this . svg . nativeElement . getBoundingClientRect ( ) ; const gElRect = this . g . nativeElement . getBoundingClientRect ( ) ; g . attr ( 'transform' , `translate(
      ${ svgRect . left - gElRect . left + svgPadding },
      ${ svgRect . top - gElRect . top + svgPadding }
    )` ) ; svg . attr ( 'height' , gElRect . height + svgPadding * 2 ) . attr ( 'width' , gElRect . width + svgPadding * 2 ) ; } ngOnChanges ( changes ) { if ( ( < any > changes ) . routerTree && this . g ) { d3 . select ( this . g . nativeElement ) . selectAll ( '*' ) . remove ( ) ; } this . render ( ) ; } private onRollover ( route ) { if ( this . selectedRoute ) { this . selectedRoute = null ; } else { this . selectedRoute = route ; } this . render ( ) ; } onRetrieveSearchResults = ( query ) : Promise < Array < any > > => { return this . userActions . searchRouter ( this . routerTree , query ) ; } ; onSelectedSearchResultChanged ( route ) { this . selectedRoute = route ; this . render ( ) ; } showReadme ( ) { window . open ( 'https://github.com/rangle/augury#known-issues' ) ; } }