import React, { Component } from 'react' // import PropTypes from 'prop-types' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' function formatLabel(label, value) { if (!value) { return label } let len = 0 return ( { label.split(new RegExp(value.replace(/[-\[\]\(\)\+\*\\\^\$\{\}\.\?\&\|\<\>]/g, ''), "i")) // eslint-disable-line .reduce((prev, current, i) => { if (!i) { len += current.length return [current] } const ret = prev.concat({label.substr(len, value.length)}, current) len += value.length + current.length return ret }, []) } ) } function sanitizeForAutocomplete(s) { return (s || "") .toLowerCase() .replace(/[^a-zA-Z0-9 ]/g, '') .trim() .replace(/\\/g, '') } class Autocomplete extends Component { constructor(props) { super() this.state = { q: props.q || "", selected: 0, matches: [] } this.handleKeyDown = this.handleKeyDown.bind(this) this.handleChange = this.handleChange.bind(this) this.handleCancel = this.handleCancel.bind(this) } componentDidMount() { // build index based on what's in the hierarchy if (!this.props.institutions.loading) this.buildIndex() } buildIndex() { const { entities, lookup } = this.props.institutions let index = [] this.index = index Object.keys(entities).forEach(name => { if (!name) return index.push([sanitizeForAutocomplete(name), name]) }) Object.keys(lookup).forEach(name => { if (!name) return index.push([sanitizeForAutocomplete(name), lookup[name]]) }) // console.log(index) // node.synonyms // .split("\n") // .map(word => word = word.trim()) // .filter(word => !!word) // .forEach(word => index.push([prefixName, name, node.id])) } componentDidUpdate(oldProps) { if (oldProps.institutions.loading && !this.props.institutions.loading) { this.buildIndex() } if (this.props.vetting !== oldProps.vetting) { this.handleChange({ target: { value: this.props.vetting } }) } else if (this.props.value !== oldProps.value) { this.setState({ q: '' }) } } handleKeyDown(e) { let name console.log(e.keyCode) switch (e.keyCode) { case 27: // escape e.preventDefault() this.handleCancel() break case 38: // up e.preventDefault() this.setState({ selected: (this.state.matches.length + this.state.selected - 1) % this.state.matches.length }) return false case 40: // down e.preventDefault() this.setState({ selected: (this.state.selected + 1) % this.state.matches.length }) return false case 13: // enter - select from the list name = this.state.matches[this.state.selected] e.preventDefault() this.handleSelect(name, true) return false case 9: // tab - keep the unverified text name = this.state.matches[this.state.selected] if (name === this.state.q) { this.handleSelect(this.state.q, true) } else { this.handleSelect(this.state.q, false) } return false default: break } return null } handleChange(e) { // search for the given string in our index const q = e.target.value let value = sanitizeForAutocomplete(q) if (!value.length) { this.setState({ q, selected: 0, matches: [], }) return } let seen = {} value.split(' ').forEach(word => { const re = new RegExp(word) this.index.forEach(([synonym, term]) => { if (synonym.match(re)) { if (synonym.indexOf(value) === 0) { if (term in seen) { seen[term] += 4 } else { seen[term] = 4 } } else if (term in seen) { seen[term] += 1 } else { seen[term] = 1 } } }) }) let matches = Object.keys(seen) .map(term => [seen[term], term]) .sort((a, b) => { return b[0] - a[0] }) .slice(0, 100) .map(pair => pair[1]) this.setState({ q, selected: 0, matches, }) } handleSelect(name, valid) { console.log('select', name, valid) if (this.props.onSelect) this.props.onSelect(name, valid) this.setState({ q: name, selected: 0, matches: [] }) } handleCancel() { if (this.props.onCancel) this.props.onCancel() this.setState({ q: '', selected: 0, matches: [] }) } render() { const { q, selected } = this.state const matches = this.state.matches.map((match, i) => { const label = formatLabel(match, q) return (