import { Workflow } from "../../" ; import { PluginBase } from "../plugin-base" ; export class SelectionPlugin extends PluginBase { static edgePortsDelimiter = "$!$" ; private svg : SVGSVGElement ; private selection = new Map < string , "edge" | "node" > ( ) ; private cleanups : Function [ ] = [ ] ; private detachModelEvents : Function ; private selectionChangeCallbacks = [ ] ; private css = { selected : "__selection-plugin-selected" , highlight : "__selection-plugin-highlight" , fade : "__selection-plugin-fade" , plugin : "__plugin-selection" } ; registerWorkflow ( workflow ) { super . registerWorkflow ( workflow ) ; this . svg = this . workflow . svgRoot ; this . svg . classList . add ( this . css . plugin ) ; const clickListener = this . onClick . bind ( this ) ; this . svg . addEventListener ( "click" , clickListener ) ; this . cleanups . push ( ( ) => this . svg . removeEventListener ( "click" , clickListener ) ) ; } afterRender ( ) { this . restoreSelection ( ) ; } afterModelChange ( ) { if ( typeof this . detachModelEvents === "function" ) { this . detachModelEvents ( ) ; } this . detachModelEvents = this . bindModelEvents ( ) ; } destroy ( ) { this . detachModelEvents ( ) ; this . detachModelEvents = undefined ; this . svg . classList . remove ( this . css . plugin ) ; for ( const fn of this . cleanups ) { fn ( ) ; } } clearSelection ( ) { const selection = this . svg . querySelectorAll ( `.${ this . css . selected }` ) ; const highlights = this . svg . querySelectorAll ( `.${ this . css . highlight }` ) ; for ( const el of selection ) { el . classList . remove ( this . css . selected ) ; } for ( const el of highlights ) { el . classList . remove ( this . css . highlight ) ; } this . svg . classList . remove ( this . css . fade ) ; this . selection . clear ( ) ; this . emitChange ( null ) ; } getSelection ( ) { return this . selection ; } registerOnSelectionChange ( fn ) { this . selectionChangeCallbacks . push ( fn ) ; } selectStep ( stepID ) { const query = `[data-connection-id="${ stepID }"]` ; const el = this . svg . querySelector ( query ) as SVGElement ; if ( el ) { this . materializeClickOnElement ( el ) ; } } private bindModelEvents ( ) { const handler = ( ) => this . restoreSelection ( ) ; const cleanup = [ ] ; const events = [ "connection.create" , "connection.remove" ] ; for ( const ev of events ) { const dispose = this . workflow . model . on ( ev as any , handler ) ; cleanup . push ( ( ) => dispose . dispose ( ) ) ; } return ( ) => cleanup . forEach ( fn => fn ( ) ) ; } private restoreSelection ( ) { this . selection . forEach ( ( type , connectionID ) => { if ( type === "node" ) { const el = this . svg . querySelector ( `[data-connection-id="${ connectionID }"]` ) as SVGElement ; if ( el ) { this . selectNode ( el ) ; } } else if ( type === "edge" ) { const [ sID , dID ] = connectionID . split ( SelectionPlugin . edgePortsDelimiter ) ; const edgeSelector = `[data-source-connection="${ sID }"][data-destination-connection="${ dID }"]` ; const edge = this . svg . querySelector ( edgeSelector ) as SVGElement ; if ( edge ) { this . selectEdge ( edge ) ; } } } ) ; } private onClick ( click ) { const target = click . target as SVGElement ; this . clearSelection ( ) ; this . materializeClickOnElement ( target ) ; } private materializeClickOnElement ( target ) { let element ; if ( element = this . workflow . findParent ( target , "node" ) ) { this . selectNode ( element ) ; this . selection . set ( element . getAttribute ( "data-connection-id" ) , "node" ) ; this . emitChange ( element ) ; } else if ( element = this . workflow . findParent ( target , "edge" ) ) { this . selectEdge ( element ) ; const cid = [ element . getAttribute ( "data-source-connection" ) , SelectionPlugin . edgePortsDelimiter , element . getAttribute ( "data-destination-connection" ) ] . join ( "" ) ; this . selection . set ( cid , "edge" ) ; this . emitChange ( cid ) ; } } private selectNode ( element ) { /**
         * Bring it to the front (there is no Z-index in svg so top element is the one latest in DOM)
         */ element . parentElement . appendChild ( element ) ; this . svg . classList . add ( this . css . fade ) ; element . classList . add ( this . css . selected ) ; element . classList . add ( this . css . highlight ) ; const nodeID = element . getAttribute ( "data-id" ) ; const adjacentEdges = this . svg . querySelectorAll ( `.edge[data-source-node="${ nodeID }"],` + `.edge[data-destination-node="${ nodeID }"]` ) ; const firstNode = this . svg . getElementsByClassName ( "node" ) [ 0 ] ; for ( const edge of adjacentEdges ) { edge . classList . add ( this . css . highlight ) ; this . workflow . workflow . insertBefore ( edge , firstNode ) ; const sourceNodeID = edge . getAttribute ( "data-source-node" ) ; const destinationNodeID = edge . getAttribute ( "data-destination-node" ) ; const connectedNodes = this . svg . querySelectorAll ( `.node[data-id="${ sourceNodeID }"],` + `.node[data-id="${ destinationNodeID }"]` ) ; for ( const n of connectedNodes ) { n . classList . add ( this . css . highlight ) ; } } } private selectEdge ( element ) { element . classList . add ( this . css . highlight ) ; element . classList . add ( this . css . selected ) ; const sourceNode = element . getAttribute ( "data-source-node" ) ; const destNode = element . getAttribute ( "data-destination-node" ) ; const sourcePort = element . getAttribute ( "data-source-port" ) ; const destPort = element . getAttribute ( "data-destination-port" ) ; const inputPortSelector = `.node[data-id="${ destNode }"] .input-port[data-port-id="${ destPort }"]` ; const outputPortSelector = `.node[data-id="${ sourceNode }"] .output-port[data-port-id="${ sourcePort }"]` ; const connectedPorts = this . svg . querySelectorAll ( `${ inputPortSelector }, ${ outputPortSelector }` ) ; for ( const port of connectedPorts ) { port . classList . add ( this . css . highlight ) ; } } private emitChange ( change ) { for ( const fn of this . selectionChangeCallbacks ) { fn ( change ) ; } } }