import { ShortcutObject , TakenByObject } from '../index' ; import * as React from 'react' ; import { classes } from 'typestyle' ; import { EN_US } from '@lumino/keyboard' ; import { InputStyle , InputUnavailableStyle , InputBoxHiddenStyle , InputBoxStyle , InputBoxNewStyle , InputTextStyle , InputWaitingStyle , InputSelectedTextStyle , SubmitNonFunctionalStyle , SubmitConflictStyle , SubmitStyle } from '../componentStyle/ShortcutInputStyle' ; export interface IShortcutInputProps { handleUpdate : Function ; deleteShortcut : Function ; toggleInput : Function ; shortcut : ShortcutObject ; shortcutId : string ; toSymbols : Function ; keyBindingsUsed : { [ index ] : TakenByObject ; } ; sortConflict : Function ; clearConflicts : Function ; displayInput : boolean ; newOrReplace : string ; placeholder : string ; } export interface IShortcutInputState { value : string ; userInput : string ; isAvailable : boolean ; isFunctional : boolean ; takenByObject : TakenByObject ; keys : Array < string > ; currentChain : string ; selected : boolean ; } export class ShortcutInput extends React . Component < IShortcutInputProps , IShortcutInputState > { constructor ( props ) { super ( props ) ; } state = { value : this . props . placeholder , userInput : '' , isAvailable : true , isFunctional : this . props . newOrReplace === 'replace' , takenByObject : new TakenByObject ( ) , keys : new Array < string > ( ) , currentChain : '' , selected : true } ; handleUpdate = ( ) => { let keys = this . state . keys ; keys . push ( this . state . currentChain ) ; this . setState ( { keys : keys } ) ; this . props . handleUpdate ( this . props . shortcut , this . state . keys ) ; } ; handleOverwrite = async ( ) => { this . props . deleteShortcut ( this . state . takenByObject . takenBy , this . state . takenByObject . takenByKey ) . then ( this . handleUpdate ( ) ) ; } ; handleReplace = async ( ) => { let keys = this . state . keys ; keys . push ( this . state . currentChain ) ; this . props . toggleInput ( ) ; await this . props . deleteShortcut ( this . props . shortcut , this . props . shortcutId ) ; this . props . handleUpdate ( this . props . shortcut , keys ) ; } ; /** Parse user input for chained shortcuts */ parseChaining = ( event , value , userInput , keys , currentChain ) : Array < any > => { let key = EN_US . keyForKeydownEvent ( event . nativeEvent ) ; const modKeys = [ 'Shift' , 'Control' , 'Alt' , 'Meta' , 'Ctrl' , 'Accel' ] ; if ( event . key === 'Backspace' ) { userInput = '' ; value = '' ; keys = [ ] ; currentChain = '' ; this . setState ( { value : value , userInput : userInput , keys : keys , currentChain : currentChain } ) ; } else if ( event . key !== 'CapsLock' ) { const lastKey = userInput . substr ( userInput . lastIndexOf ( ' ' ) + 1 , userInput . length ) . trim ( ) ; /** if last key was not a modefier then there is a chain */ if ( modKeys . lastIndexOf ( lastKey ) === - 1 && lastKey != '' ) { userInput = userInput + ',' ; keys . push ( currentChain ) ; currentChain = '' ; /** check if a modefier key was held down through chain */ if ( event . ctrlKey && event . key != 'Control' ) { userInput = ( userInput + ' Ctrl' ) . trim ( ) ; currentChain = ( currentChain + ' Ctrl' ) . trim ( ) ; } if ( event . metaKey && event . key != 'Meta' ) { userInput = ( userInput + ' Accel' ) . trim ( ) ; currentChain = ( currentChain + ' Accel' ) . trim ( ) ; } if ( event . altKey && event . key != 'Alt' ) { userInput = ( userInput + ' Alt' ) . trim ( ) ; currentChain = ( currentChain + ' Alt' ) . trim ( ) ; } if ( event . shiftKey && event . key != 'Shift' ) { userInput = ( userInput + ' Shift' ) . trim ( ) ; currentChain = ( currentChain + ' Shift' ) . trim ( ) ; } /** if not a modefier key, add to user input and current chain */ if ( modKeys . lastIndexOf ( event . key ) === - 1 ) { userInput = ( userInput + ' ' + key ) . trim ( ) ; currentChain = ( currentChain + ' ' + key ) . trim ( ) ; } else { if ( event . key === 'Meta' ) { userInput = ( userInput + ' Accel' ) . trim ( ) ; currentChain = ( currentChain + ' Accel' ) . trim ( ) ; } else if ( event . key === 'Control' ) { userInput = ( userInput + ' Ctrl' ) . trim ( ) ; currentChain = ( currentChain + ' Ctrl' ) . trim ( ) ; } else if ( event . key === 'Shift' ) { userInput = ( userInput + ' Shift' ) . trim ( ) ; currentChain = ( currentChain + ' Shift' ) . trim ( ) ; } else if ( event . key === 'Alt' ) { userInput = ( userInput + ' Alt' ) . trim ( ) ; currentChain = ( currentChain + ' Alt' ) . trim ( ) ; } else { userInput = ( userInput + ' ' + event . key ) . trim ( ) ; currentChain = ( currentChain + ' ' + event . key ) . trim ( ) ; } } } else { /** if modefier key, rename */ if ( event . key === 'Control' ) { userInput = ( userInput + ' Ctrl' ) . trim ( ) ; currentChain = ( currentChain + ' Ctrl' ) . trim ( ) ; } else if ( event . key === 'Meta' ) { userInput = ( userInput + ' Accel' ) . trim ( ) ; currentChain = ( currentChain + ' Accel' ) . trim ( ) ; } else if ( event . key === 'Shift' ) { userInput = ( userInput + ' Shift' ) . trim ( ) ; currentChain = ( currentChain + ' Shift' ) . trim ( ) ; } else if ( event . key === 'Alt' ) { userInput = ( userInput + ' Alt' ) . trim ( ) ; currentChain = ( currentChain + ' Alt' ) . trim ( ) ; } else { userInput = ( userInput + ' ' + key ) . trim ( ) ; currentChain = ( currentChain + ' ' + key ) . trim ( ) ; } } } /** update state of keys and currentChain */ this . setState ( { keys : keys , currentChain : currentChain } ) ; return [ userInput , keys , currentChain ] ; } ; /**
     * Check if shorcut being typed will work
     * (does not end with ctrl, alt, command, or shift)
     * */ checkNonFunctional = ( shortcut ) : boolean => { const dontEnd = [ 'Ctrl' , 'Alt' , 'Accel' , 'Shift' ] ; const shortcutKeys = this . state . currentChain . split ( ' ' ) ; const last = shortcutKeys [ shortcutKeys . length - 1 ] ; this . setState ( { isFunctional : ! ( dontEnd . indexOf ( last ) !== - 1 ) } ) ; return dontEnd . indexOf ( last ) !== - 1 ; } ; /** Check if shortcut being typed is already taken */ checkShortcutAvailability = ( userInput , keys , currentChain ) : TakenByObject => { /** First, check whole shortcut */ let isAvailable = Object . keys ( this . props . keyBindingsUsed ) . indexOf ( keys . join ( ' ' ) + currentChain + '_' + this . props . shortcut . selector ) === - 1 || userInput === '' ; let takenByObject = new TakenByObject ( ) ; if ( isAvailable ) { /** Next, check each piece of a chain */ for ( let binding of keys ) { if ( Object . keys ( this . props . keyBindingsUsed ) . indexOf ( binding + '_' + this . props . shortcut . selector ) !== - 1 && binding !== '' ) { isAvailable = false ; takenByObject = this . props . keyBindingsUsed [ binding + '_' + this . props . shortcut . selector ] ; break ; } } /** Check current chain */ if ( isAvailable && Object . keys ( this . props . keyBindingsUsed ) . indexOf ( currentChain + '_' + this . props . shortcut . selector ) !== - 1 && currentChain !== '' ) { isAvailable = false ; takenByObject = this . props . keyBindingsUsed [ currentChain + '_' + this . props . shortcut . selector ] ; } } else { takenByObject = this . props . keyBindingsUsed [ keys . join ( ' ' ) + currentChain + '_' + this . props . shortcut . selector ] ; } /** allow to set shortcut to what it initially was if replacing */ if ( ! isAvailable ) { if ( takenByObject . takenBy . id === this . props . shortcut . id && this . props . newOrReplace === 'replace' ) { isAvailable = true ; takenByObject = new TakenByObject ( ) ; } } this . setState ( { isAvailable : isAvailable } ) ; return takenByObject ; } ; checkConflict ( takenByObject , keys ) { if ( takenByObject . id !== '' && takenByObject . takenBy . id !== this . props . shortcut . id ) { this . props . sortConflict ( this . props . shortcut , takenByObject , takenByObject . takenByLabel , '' ) ; } else { this . props . clearConflicts ( ) ; } } /** Parse and normalize user input */ handleInput = ( event ) : void => { event . preventDefault ( ) ; this . setState ( { selected : false } ) ; const parsed = this . parseChaining ( event , this . state . value , this . state . userInput , this . state . keys , this . state . currentChain ) ; const userInput = parsed [ 0 ] ; const keys = parsed [ 1 ] ; const currentChain = parsed [ 2 ] ; const value = this . props . toSymbols ( userInput ) ; let takenByObject = this . checkShortcutAvailability ( userInput , keys , currentChain ) ; this . checkConflict ( takenByObject , keys ) ; this . setState ( { value : value , userInput : userInput , takenByObject : takenByObject , keys : keys , currentChain : currentChain } , ( ) => this . checkNonFunctional ( this . state . userInput ) ) ; } ; handleBlur = ( event ) => { if ( event . relatedTarget === null || ( ( event . relatedTarget as HTMLElement ) . id !== 'no-blur' && ( event . relatedTarget as HTMLElement ) . id !== 'overwrite' ) ) { this . props . toggleInput ( ) ; this . setState ( { value : '' , userInput : '' } ) ; this . props . clearConflicts ( ) ; } } ; render ( ) { let inputClassName = InputStyle ; if ( ! this . state . isAvailable ) { inputClassName = classes ( inputClassName , InputUnavailableStyle ) ; } return ( < div className = { this . props . displayInput ? this . props . newOrReplace === 'new' ? classes ( InputBoxStyle , InputBoxNewStyle ) : InputBoxStyle : InputBoxHiddenStyle } onBlur = { event => this . handleBlur ( event ) } > < div tabIndex = { 0 } id = "no-blur" className = { inputClassName } onKeyDown = { this . handleInput } ref = { input => input && input . focus ( ) } > < p className = { this . state . selected && this . props . newOrReplace === 'replace' ? classes ( InputTextStyle , InputSelectedTextStyle ) : this . state . value === '' ? classes ( InputTextStyle , InputWaitingStyle ) : InputTextStyle } > { this . state . value === '' ? 'press keys' : this . state . value } < /p> < /div> < button className = { ! this . state . isFunctional ? classes ( SubmitStyle , SubmitNonFunctionalStyle ) : ! this . state . isAvailable ? classes ( SubmitStyle , SubmitConflictStyle ) : classes ( SubmitStyle ) } id = { 'no-blur' } disabled = { ! this . state . isAvailable || ! this . state . isFunctional } onClick = { ( ) => { if ( this . props . newOrReplace === 'new' ) { this . handleUpdate ( ) ; this . setState ( { value : '' , keys : [ ] , currentChain : '' } ) ; this . props . toggleInput ( ) ; } else { /** don't replace if field has not been edited */ if ( this . state . selected ) { this . props . toggleInput ( ) ; this . setState ( { value : '' , userInput : '' } ) ; this . props . clearConflicts ( ) ; } else { this . handleReplace ( ) ; } } } } /> { ! this . state . isAvailable && ( < button hidden id = "overwrite" onClick = { ( ) => { this . handleOverwrite ( ) ; this . props . clearConflicts ( ) ; this . props . toggleInput ( ) ; } } > Overwrite < /button>)} < /div>); } }