import { find , forOwn , fromPairs , omit , partition , reduce , toPairs , values } from "lodash" ; import { get , isString , isEmpty , negate } from "lodash/fp" ; import { Validator } from "collit" ; import * as React from "react" ; import * as ReactDOM from "react-dom" ; import { Kaavio } from "kaavio/esnext/Kaavio" ; import * as themePlain from "./themes/plain/theme" ; import * as themeDark from "./themes/dark/theme" ; const ContainerCustomStyle = require ( "./themes/styles/Container.css" ) ; const DiagramCustomStylePlain = require ( "./themes/styles/Diagram.plain.css" ) ; const DiagramCustomStyleDark = require ( "./themes/styles/Diagram.dark.css" ) ; const themeFor = { plain : { containerStyle : ContainerCustomStyle , diagramStyle : DiagramCustomStylePlain , ... themePlain } , dark : { containerStyle : ContainerCustomStyle , diagramStyle : DiagramCustomStyleDark , ... themeDark } } ; const Fragment = React [ "Fragment" ] ; export interface PvjsProps { theme : "plain" | "dark" ; pathway : Record < string , any > ; entitiesById : Record < string , any > ; opacities : string [ ] ; highlights : string [ ] ; panned : string [ ] ; zoomed : string [ ] ; detailPanelOpen : boolean ; selected : null | Record < string , any > ; } function createPublicationXrefString ( displayNumbers ) { var publicationXrefString ; if ( displayNumbers . length === 1 ) { publicationXrefString = displayNumbers [ 0 ] ; } else { displayNumbers . sort ( function ( a , b ) { return a - b ; } ) ; var i = 0 ; publicationXrefString = displayNumbers [ i ] . toString ( ) ; if ( displayNumbers . length > 2 ) { do { i += 1 ; if ( displayNumbers [ i - 1 ] + 1 !== displayNumbers [ i ] || displayNumbers [ i ] + 1 !== displayNumbers [ i + 1 ] ) { if ( i !== 1 ) { if ( displayNumbers [ i - 2 ] + 2 === displayNumbers [ i ] ) { publicationXrefString += "-" + displayNumbers [ i ] . toString ( ) ; } else { publicationXrefString += ", " + displayNumbers [ i ] . toString ( ) ; } } else { publicationXrefString += ", " + displayNumbers [ i ] . toString ( ) ; } } } while ( i < displayNumbers . length - 2 ) ; } i += 1 ; if ( displayNumbers [ i - 2 ] + 2 === displayNumbers [ i ] ) { publicationXrefString += "-" + displayNumbers [ i ] . toString ( ) ; } else { publicationXrefString += ", " + displayNumbers [ i ] . toString ( ) ; } } return publicationXrefString ; } function normalizeTargetValue ( targetValue ) { return targetValue . replace ( /[^a-zA-Z0-9]/g , "" ) . toUpperCase ( ) ; } function cleanQueryParam ( input ) { return input . replace ( /[^\w\-\:]/g , "" ) ; } const pluralForOperationName = { highlight : "highlights" , opacity : "opacities" } ; const parseForOperationName = { highlight : function ( h ) { if ( Validator . isColor ( h ) ) { return h ; } const hexed = `#${ h }` ; if ( Validator . isColor ( hexed ) ) { return hexed ; } else { console . warn ( `Cannot parse color "${ h }"` ) ; } } , opacity : parseFloat } ; function reconcileQueryParam ( operationNameSingular , styleValuesByNormalizedTargetValues , queryParamKey , queryParamValue ) { let result = [ ] ; if ( queryParamKey . indexOf ( `${ operationNameSingular }-` ) === 0 ) { const targetValue = queryParamKey . replace ( `${ operationNameSingular }-` , "" ) ; const normalizedTargetValue = normalizeTargetValue ( targetValue ) ; if ( normalizedTargetValue in styleValuesByNormalizedTargetValues ) { const prop = styleValuesByNormalizedTargetValues [ normalizedTargetValue ] ; console . warn ( `Warning: "${ operationNameSingular }" was specified by two different sources, which may or may not conflict.
				prop passed to Pvjs class:
					${ operationNameSingular }={${ JSON . stringify ( prop ) }}
				URL query param:
					${ queryParamKey }=${ queryParamValue }
				Setting URL query param to match prop passed to Pvjs class.` ) ; const searchParams = new URLSearchParams ( location . search ) ; searchParams . set ( queryParamKey , prop ) ; history . replaceState ( { queryParamKey } , document . title , "?" + searchParams . toString ( ) ) ; } else { result . push ( [ null , cleanQueryParam ( targetValue ) , parseForOperationName [ operationNameSingular ] ( cleanQueryParam ( queryParamValue ) ) ] ) ; } } return result ; } export class Pvjs extends React . Component < any , any > { kaavioRef : any ; detailsPanelRef : any ; static defaultProps = { theme : "plain" , opacities : [ ] , highlights : [ ] , panned : [ ] , zoomed : [ ] , pathway : { } , entitiesById : { } , detailPanelOpen : false , selected : null } ; constructor ( props ) { super ( props ) ; const updatedProps = this . reconcileQueryParams ( props ) ; this . state = { detailPanelOpen : props . detailPanelOpen , selected : props . selected } ; } reconcileQueryParams ( props ) { const searchParams = Array . from ( new URLSearchParams ( location . search ) ) ; return toPairs ( pluralForOperationName ) . reduce ( function ( acc , [ operationNameSingular , operationNamePlural ] ) { const styleValuesByNormalizedTargetValues = props [ operationNamePlural ] . reduce ( ( acc , [ targetKey , targetValue , styleValue ] ) => { acc [ targetValue ] = styleValue ; return acc ; } , { } ) ; acc . push ( { operationNameSingular , operationNamePlural , styleValuesByNormalizedTargetValues } ) ; return acc ; } , [ ] ) . reduce ( function ( acc , { operationNameSingular , operationNamePlural , styleValuesByNormalizedTargetValues } ) { searchParams . forEach ( function ( [ queryParamKey , queryParamValue ] ) { reconcileQueryParam ( operationNameSingular , styleValuesByNormalizedTargetValues , queryParamKey , queryParamValue ) . forEach ( function ( item ) { if ( ! ( operationNamePlural in acc ) ) { acc [ operationNamePlural ] = [ ] ; } acc [ operationNamePlural ] . push ( item ) ; } ) ; } ) ; return acc ; } , props ) ; } closeActive ( ) { this . setState ( { selected : null , detailPanelOpen : false } ) ; } handleEntityClick = entity => { const { onEntityClick , detailPanelEnabled = true } = this . props ; if ( onEntityClick ) onEntityClick ( entity ) ; if ( entity . type . indexOf ( "DataNode" ) > - 1 && entity . xrefIdentifier && entity . xrefDataSource ) { this . setState ( { selected : entity , detailPanelOpen : detailPanelEnabled } ) ; } } ; componentWillUnmount ( ) { } onKaavioReady ( ) { const { onReady } = this . props ; if ( onReady ) { const { pathway , entitiesById } = this . state ; onReady ( entitiesById ) ; } } handleCloseDetailsPanel ( ) { this . setState ( { detailPanelOpen : false } ) ; } renderDetailsPanel ( ) { const { organism } = this . props . pathway ; const { selected , detailPanelOpen } = this . state ; if ( ! detailPanelOpen ) return null ; if ( window . hasOwnProperty ( "XrefPanel" ) ) { if ( ! ! selected . xrefDataSource && ! ! selected . xrefIdentifier ) { window [ "XrefPanel" ] [ "show" ] ( this . detailsPanelRef , selected . xrefIdentifier , selected . xrefDataSource , organism , { "0" : selected . textContent } ) ; } } } render ( ) { const { pathway , entitiesById , theme , wpId , showPanZoomControls , highlights , opacities , zoomed , panned , panCoordinates , zoomLevel , onPanZoomChange , panZoomLocked } = this . props ; return ( < Fragment > < div ref = { div => { this . detailsPanelRef = div ; } } /> { this . renderDetailsPanel ( ) } < Kaavio theme = { themeFor [ theme ] } pathway = { pathway } entitiesById = { entitiesById } onReady = { kaavio => this . onKaavioReady ( ) } onEntityClick = { this . handleEntityClick } highlights = { highlights } opacities = { opacities } panned = { panned } zoomed = { zoomed } panCoordinates = { panCoordinates } zoomLevel = { zoomLevel } onPanZoomChange = { onPanZoomChange } panZoomLocked = { panZoomLocked } showPanZoomControls = { showPanZoomControls } / > < /Fragment>); } }