import React from 'react' ; import { SearchInput } from '@patternfly/react-core' ; import { FoundInPageResult , FindInPageOptions } from 'electron' ; import '../../web/scss/components/Search/Search.scss' ; type Props = { } ; interface State { isActive : boolean ; result : FoundInPageResult ; currentMatchIdx : number ; } export default class Search extends React . Component < Props , State > { private _input : HTMLInputElement ; public constructor ( props ) { super ( props ) ; this . stopFindInPage ( ) ; this . initEvents ( ) ; this . state = { isActive : false , result : undefined , currentMatchIdx : 1 } ; } /** stop findInPage, and clear selections in page */ private async stopFindInPage ( ) { return import ( 'electron' ) . then ( async ( { remote } ) => { await remote . getCurrentWebContents ( ) . stopFindInPage ( 'activateSelection' ) ; } ) ; } private initEvents ( ) { document . body . addEventListener ( 'keydown' , evt => { if ( ! evt . defaultPrevented && evt . code === 'KeyF' && ( ( evt . ctrlKey && process . platform !== 'darwin' ) || evt . metaKey ) ) { if ( this . state . isActive && document . activeElement !== this . _input ) { this . doFocus ( ) ; } else { this . setState ( curState => { this . findInPage ( ) ; const isActive = ! curState . isActive ; if ( ! isActive ) { this . stopFindInPage ( ) ; } return { isActive , result : undefined } ; } ) ; } } } ) ; } private _onChange = this . onChange . bind ( this ) ; private async onChange ( ) { if ( this . _input ) { if ( this . _input . value . length === 0 ) { await this . stopFindInPage ( ) ; this . setState ( { result : undefined } ) ; } else { this . findInPage ( ) ; } } } private async findInPage ( options ? ) { const { remote } = await import ( 'electron' ) ; remote . getCurrentWebContents ( ) . once ( 'found-in-page' , async ( event , result ) => { this . setState ( curState => { if ( curState . isActive ) { if ( ! options ) { this . hack ( ) ; } return { result } ; } } ) ; } ) ; remote . getCurrentWebContents ( ) . findInPage ( this . _input . value , options ) ; } /** findInPage api seems to result in a loss of focus */ private hack ( ) { const v = this . _input . value ; this . _input . value = '' ; this . _input . value = v ; this . _input . focus ( ) ; } private doFocus ( ) { if ( this . state . isActive && this . _input ) { this . _input . focus ( ) ; } } private readonly _onClear = this . onClear . bind ( this ) ; private async onClear ( ) { await this . stopFindInPage ( ) ; if ( this . _input ) { this . _input . value = '' ; } this . setState ( { result : undefined , isActive : false , currentMatchIdx : 1 } ) ; } private readonly _onNext = this . onNext . bind ( this ) ; private async onNext ( ) { if ( this . state . currentMatchIdx < this . state . result . matches ) { await this . findInPage ( { forward : true , findNext : false } ) ; this . setState ( prevState => { const newCurrentResult = prevState . currentMatchIdx + 1 ; return { currentMatchIdx : newCurrentResult <= prevState . result . matches ? newCurrentResult : prevState . result . matches } ; } ) ; } } private readonly _onPrevious = this . onPrevious . bind ( this ) ; private async onPrevious ( ) { if ( this . state . currentMatchIdx > this . state . result . matches - this . state . result . matches + 1 ) { await this . findInPage ( { forward : false , findNext : false } ) ; this . setState ( prevState => { const newCurrentResult = prevState . currentMatchIdx - 1 ; return { currentMatchIdx : newCurrentResult > 0 ? newCurrentResult : 1 } ; } ) ; } } private readonly _onRef = ( c ) => { if ( c ) { this . _input = c ; this . doFocus ( ) ; } } ; private resultsRender ( ) { if ( this . state . currentMatchIdx && this . state . result ) { const curResultDisplay = this . state . currentMatchIdx . toString ( ) ; const totalResult = this . state . result . matches . toString ( ) ; return curResultDisplay + '/' + totalResult ; } } public render ( ) { if ( ! this . state . isActive ) { return < React . Fragment />; } else { return ( < SearchInput id = "search-bar" className = "kui--search" placeholder = "Find by name" value = { this . _input && this . _input . value } aria - label = "Search" onChange = { this . _onChange } onClear = { this . _onClear } spellCheck = { false } resultsCount = { this . resultsRender ( ) } onNextClick = { this . _onNext } onPreviousClick = { this . _onPrevious } ref = { this . _onRef } / > ) ; } } }