UNPKG

@ima/devtools

Version:

IMA.js debugging panel in the Chrome Developer Tools window.

207 lines (185 loc) 5.18 kB
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button'; import { Tooltip } from '@reach/tooltip'; import cn from 'clsx'; import debounce from 'lodash.debounce'; import PropTypes from 'prop-types'; import React from 'react'; import { Icon } from '@/components/atom'; import styles from './search.module.less'; /** * Wait time for debounce function for setting search query into global state. * Generally we want to wait till user finishes typing query and then set it globally * for filtering. There's no point doing this on every new key typed. * * @type {number} Wait time in ms. */ const DEBOUNCE_SET_QUERY = 250; export default class Search extends React.PureComponent { static get propTypes() { return { entriesLength: PropTypes.number, showingLength: PropTypes.number, searchQuery: PropTypes.string, hasNext: PropTypes.bool, hasPrevious: PropTypes.bool, clearEntries: PropTypes.func, setSearchQuery: PropTypes.func, selectNext: PropTypes.func, selectPrevious: PropTypes.func, }; } constructor(props) { super(props); this._setQuery = debounce(() => { const query = this._validateQuery(this.state.query); if (query !== null) { this.props.setSearchQuery(query); this.setState({ invalid: false }); } else { this.setState({ invalid: true }); } }, DEBOUNCE_SET_QUERY); this.state = { query: props.searchQuery, invalid: false, }; } render() { return ( <div className={styles.toolbar}> {this._renderSearchInput()} <span className={styles.separator} /> {this._renderActionButtons()} <span className={styles.separator} /> {this._renderClearButton()} <span className={styles.separator} /> {this._renderMenu()} </div> ); } _renderSearchInput() { const { showingLength, entriesLength } = this.props; const { query, invalid } = this.state; return ( <div className={styles.search}> <span className={cn( styles['icon'], styles['icon--large'], styles['icon--disabled'] )} > <Icon name='search' /> </span> <input type='text' value={query} onChange={e => this.onChange(e)} className={cn(styles.searchInput, { [styles['searchInput--invalid']]: invalid, })} placeholder='Search (text or /regex/)' /> <span className={styles.showing}> showing&nbsp;{showingLength}/{entriesLength}&nbsp;items </span> </div> ); } _renderActionButtons() { const { hasNext, hasPrevious, selectNext, selectPrevious } = this.props; const { query } = this.state; return ( <> <Tooltip label='Select previous item'> <button disabled={!hasPrevious} onClick={selectPrevious} className={cn(styles.btn, styles.icon)} > <Icon name='arrowUp' /> </button> </Tooltip> <Tooltip label='Select next item'> <button disabled={!hasNext} onClick={selectNext} className={cn(styles.btn, styles.icon)} > <Icon name='arrowDown' /> </button> </Tooltip> <Tooltip label='Clear search'> <button disabled={query.length <= 0} onClick={e => this.onClear(e)} className={cn(styles.btn, styles.icon)} > <Icon name='close' /> </button> </Tooltip> </> ); } _renderClearButton() { const { clearEntries } = this.props; return ( <Tooltip label='Clear entries'> <button onClick={clearEntries} className={cn(styles.btn, styles.icon)}> <Icon name='reset' /> </button> </Tooltip> ); } _renderMenu() { return ( <Menu> <Tooltip label='More options'> <MenuButton className={cn(styles.btn, styles.icon)}> <Icon name='more' /> </MenuButton> </Tooltip> <MenuList> <MenuItem onSelect={() => chrome.runtime.openOptionsPage()}> <span className='menu-item__label'>Settings</span> <Icon name='cog' /> </MenuItem> </MenuList> </Menu> ); } onChange({ target: { value } }) { this.setState( { query: value, }, this._setQuery ); } onClear(e) { e.preventDefault(); this.props.setSearchQuery(''); this.setState({ query: '', }); } _validateQuery(query) { try { if (query[0] !== '/') { return query; } else { // Ignore search if regexp is not yet complete or invalid if (!/\/[\s\S]+\/[\s\S]*/.test(query)) { return null; } // Test if regexp is correct // eslint-disable-next-line no-constant-condition if (new RegExp(query)) { return query; } } } catch (e) { return null; } } }