import * as d3 from 'd3' ; import { ActionCreators as historyActions } from 'redux-undo' ; import { getType } from 'typesafe-actions' ; import { baseSelectMark , expandLayers , selectMark , InspectorSelectedType } from '../actions/inspectorActions' ; import { deleteMark } from '../actions/markActions' ; import { store } from '../store' ; import { MODE , SELECTED } from '../store/factory/Signal' ; import { getParentGroupIds } from '../util/hierarchy' ; import sg from './signals' ; const ACTIONS = require ( '../actions/Names' ) ; const ctrl = require ( './' ) ; module . exports = { onSignal , offSignal , onSignalInGroup , offSignalInGroup , setSignalInGroup , getSignalInGroup , register : registerSignalListeners } ; const listeners = { } ; const groupListeners = { } ; d3 . select ( window ) . on ( 'keydown' , function ( ) { const evt = d3 . event ; ( window as any ) . _inputKeyboard = { keycode : evt . keyCode , _key : evt . key } ; handleHistory ( evt ) ; handleDelete ( evt ) ; } ) . on ( 'keyup' , function ( ) { ( window as any ) . _inputKeyboard = null ; } ) . on ( 'beforeunload' , beforeUnload ) ; param { string } name param { Function } handler returns { Object } export function onSignal ( name , handler ) { listeners [ name ] = listeners [ name ] || [ ] ; listeners [ name ] . push ( handler ) ; if ( ctrl . view ) { ctrl . view . addSignalListener ( name , handler ) ; } return ctrl ; } param { string } name param { Function } handler returns { Object } export function offSignal ( name , handler ) { listeners [ name ] = listeners [ name ] || [ ] ; const listener = listeners [ name ] ; for ( let i = listener . length ; -- i >= 0 ; ) { if ( ! handler || listener [ i ] === handler ) { listener . splice ( i , 1 ) ; } } if ( ctrl . view ) { ctrl . view . removeSignalListener ( name , handler ) ; } return ctrl ; } function getSignalOperatorInGroup ( view , groupName , signalName ) { const rootItemNode = view . _scenegraph . root . items [ 0 ] ; for ( const markNode of rootItemNode . items ) { if ( markNode . name && markNode . marktype === 'group' ) { if ( markNode . name === groupName ) { if ( markNode . items && markNode . items . length ) { const itemNode = markNode . items [ 0 ] ; const signals = itemNode . context . signals ; for ( const name of Object . keys ( signals ) ) { if ( name === signalName ) { return signals [ name ] ; } } } } } } return null ; } export function onSignalInGroup ( view , groupName , signalName , handler ) { const operator = getSignalOperatorInGroup ( view , groupName , signalName ) ; if ( operator ) { const h = function ( ) { handler ( signalName , operator . value ) ; } ; h . handler = handler ; groupListeners [ groupName ] = groupListeners [ groupName ] || { } ; groupListeners [ groupName ] [ signalName ] = groupListeners [ groupName ] [ signalName ] || [ ] ; groupListeners [ groupName ] [ signalName ] . push ( { operator , handler : h } ) ; view . on ( operator , null , h ) ; } } export function offSignalInGroup ( view , groupName , signalName , handler ) { const operator = getSignalOperatorInGroup ( view , groupName , signalName ) ; if ( operator ) { groupListeners [ groupName ] = groupListeners [ groupName ] || { } ; groupListeners [ groupName ] [ signalName ] = groupListeners [ groupName ] [ signalName ] || [ ] ; const listener = groupListeners [ groupName ] [ signalName ] ; for ( let i = listener . length ; -- i >= 0 ; ) { if ( ! handler || listener [ i ] . handler === handler ) { listener [ i ] . operator . _targets . remove ( handler ) ; listener . splice ( i , 1 ) ; } } } } export function setSignalInGroup ( view , groupName , signalName , value ) { const operator = getSignalOperatorInGroup ( view , groupName , signalName ) ; view . update ( operator , value ) ; view . runAsync ( ) ; } export function getSignalInGroup ( view , groupName , signalName ) { const operator = getSignalOperatorInGroup ( view , groupName , signalName ) ; if ( operator ) { return operator . value ; } return null ; } returns { void } export function registerSignalListeners ( ) { const win = d3 . select ( window ) ; const dragover = 'dragover.altchan' ; if ( ! win . on ( dragover ) ) { win . on ( dragover , function ( ) { const mode = sg . get ( MODE ) ; const shiftKey = d3 . event . shiftKey ; const channels = mode === 'channels' ; const altchannels = mode === 'altchannels' ; if ( ! channels && ! altchannels ) { return ; } if ( channels && shiftKey ) { sg . set ( MODE , 'altchannels' ) ; ctrl . update ( ) ; } else if ( altchannels && ! shiftKey ) { sg . set ( MODE , 'channels' ) ; ctrl . update ( ) ; } } ) ; } if ( ctrl . view ) { ctrl . view . addSignalListener ( SELECTED , function ( name , selected ) { const role = selected . mark . role ; const id = role && + role . split ( 'lyra_' ) [ 1 ] ; const state = store . getState ( ) ; const selectedId = state . getIn ( [ 'inspector' , 'encodings' , 'selectedId' ] ) ; const selectedType = state . getIn ( [ 'inspector' , 'encodings' , 'selectedType' ] ) ; if ( selectedId === id || selectedType === InspectorSelectedType . SELECT_INTERACTION ) { return ; } const parentLayerIds = getParentGroupIds ( id , state ) ; if ( id ) { store . dispatch ( baseSelectMark ( id , parentLayerIds ) ) ; store . dispatch ( expandLayers ( parentLayerIds ) ) ; } } ) ; Object . keys ( listeners ) . forEach ( function ( signalName ) { if ( ! ctrl . view . _signals [ signalName ] ) { listeners [ signalName ] = [ ] ; } else { listeners [ signalName ] . forEach ( function ( handlerFn ) { ctrl . view . addSignalListener ( signalName , handlerFn ) ; } ) ; } } ) ; Object . keys ( groupListeners ) . forEach ( function ( groupName ) { Object . keys ( groupListeners [ groupName ] ) . forEach ( function ( signalName ) { const maybeSignal = getSignalOperatorInGroup ( ctrl . view , groupName , signalName ) ; if ( ! maybeSignal ) { groupListeners [ groupName ] [ signalName ] = [ ] ; } else { groupListeners [ groupName ] [ signalName ] . forEach ( function ( def ) { ctrl . view . on ( def . operator , null , def . handler ) ; } ) ; } } ) ; } ) ; } } param { Event } evt returns { boolean } function testInput ( evt ) { const target = evt . srcElement || evt . target ; const tagName = target . tagName . toUpperCase ( ) ; if ( tagName === 'INPUT' || tagName === 'TEXTAREA' || target . contentEditable === 'true' ) { return ! target . readOnly && ! target . disabled ; } return false ; } param { Event } evt returns { boolean } function handleHistory ( evt ) { const keyCode = evt . keyCode ; if ( ( ! testInput ( evt ) && evt . metaKey === true ) || evt . ctrlKey === true ) { if ( keyCode === 89 ) { store . dispatch ( historyActions . redo ( ) ) ; evt . preventDefault ( ) ; return false ; } else if ( keyCode === 90 ) { store . dispatch ( evt . shiftKey ? historyActions . redo ( ) : historyActions . undo ( ) ) ; evt . preventDefault ( ) ; return false ; } } } param { Event } evt returns { boolean } function handleDelete ( evt ) { if ( ! testInput ( evt ) && evt . keyCode === 8 ) { const state = store . getState ( ) ; const inspectors = state . getIn ( [ 'inspector' , 'encodings' ] ) ; const type = inspectors . get ( 'selectedType' ) ; const id = inspectors . get ( 'selectedId' ) ; const groupId = state . getIn ( [ 'vis' , 'present' , 'marks' , String ( id ) , '_parent' ] ) ; if ( type === getType ( baseSelectMark ) ) { evt . preventDefault ( ) ; store . dispatch ( selectMark ( groupId ) as any ) ; store . dispatch ( deleteMark ( id ) as any ) ; return false ; } } } returns { string } function beforeUnload ( ) { if ( process . env . NODE_ENV === 'production' ) { const msg = 'You have unsaved changed in Lyra.' ; d3 . event . returnValue = msg ; return msg ; } }