diff --git a/browser/main/Detail/FolderSelect.js b/browser/main/Detail/FolderSelect.js new file mode 100644 index 00000000..47fa6bad --- /dev/null +++ b/browser/main/Detail/FolderSelect.js @@ -0,0 +1,291 @@ +import React, { PropTypes } from 'react' +import CSSModules from 'browser/lib/CSSModules' +import styles from './FolderSelect.styl' +import _ from 'lodash' + +class FolderSelect extends React.Component { + constructor (props) { + super(props) + + this.state = { + status: 'IDLE', + search: '', + optionIndex: -1 + } + } + + componentDidMount () { + this.value = this.props.value + } + + componentDidUpdate () { + this.value = this.props.value + } + + handleClick (e) { + this.setState({ + status: 'SEARCH', + optionIndex: -1 + }, () => { + this.refs.search.focus() + }) + } + + handleFocus (e) { + if (this.state.status === 'IDLE') { + this.setState({ + status: 'FOCUS' + }) + } + } + + handleBlur (e) { + if (this.state.status === 'FOCUS') { + this.setState({ + status: 'IDLE' + }) + } + } + + handleKeyDown (e) { + switch (e.keyCode) { + case 13: + if (this.state.status === 'FOCUS') { + this.setState({ + status: 'SEARCH', + optionIndex: -1 + }, () => { + this.refs.search.focus() + }) + } + break + case 40: + case 38: + if (this.state.status === 'FOCUS') { + this.setState({ + status: 'SEARCH', + optionIndex: 0 + }, () => { + this.refs.search.focus() + }) + } + break + case 9: + if (e.shiftKey) { + e.preventDefault() + let tabbable = document.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])') + let previousEl = tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1] + if (previousEl != null) previousEl.focus() + } + } + } + + handleSearchInputBlur (e) { + if (e.relatedTarget !== this.refs.root) { + this.setState({ + status: 'IDLE' + }) + } + } + + handleSearchInputChange (e) { + let { folders } = this.props + let search = this.refs.search.value + let optionIndex = search.length > 0 + ? _.findIndex(folders, (folder) => { + return folder.name.match(new RegExp('^' + _.escapeRegExp(search), 'i')) + }) + : -1 + + this.setState({ + search: this.refs.search.value, + optionIndex: optionIndex + }) + } + + handleSearchInputKeyDown (e) { + switch (e.keyCode) { + case 40: + e.stopPropagation() + this.nextOption() + break + case 38: + e.stopPropagation() + this.previousOption() + break + case 13: + e.stopPropagation() + this.selectOption() + break + case 27: + e.stopPropagation() + this.setState({ + status: 'FOCUS' + }, () => { + this.refs.root.focus() + }) + } + } + + nextOption () { + let { storages } = this.props + let { optionIndex } = this.state + + optionIndex++ + if (optionIndex >= folders.length) optionIndex = 0 + + this.setState({ + optionIndex + }) + } + + previousOption () { + let { folders } = this.props + let { optionIndex } = this.state + + optionIndex-- + if (optionIndex < 0) optionIndex = folders.length - 1 + + this.setState({ + optionIndex + }) + } + + selectOption () { + let { folders } = this.props + let optionIndex = this.state.optionIndex + + let folder = folders[optionIndex] + if (folder != null) { + this.setState({ + status: 'FOCUS' + }, () => { + this.setValue(folder.key) + this.refs.root.focus() + }) + } + } + + handleOptionClick (storageKey, folderKey) { + return (e) => { + e.stopPropagation() + this.setState({ + status: 'FOCUS' + }, () => { + this.setValue(storageKey + '-' + folderKey) + this.refs.root.focus() + }) + } + } + + setValue (value) { + this.value = value + this.props.onChange() + } + + render () { + let { className, data, value } = this.props + let splitted = value.split('-') + let storageKey = splitted.shift() + let folderKey = splitted.shift() + let options = [] + data.storageMap.forEach((storage, index) => { + storage.folders.forEach((folder) => { + options.push({ + storage: storage, + folder: folder + }) + }) + }) + + let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0] + + if (this.state.search.trim().length > 0) { + let filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i') + options = options.filter((option) => filter.test(option.folder.name)) + } + + let optionList = options + .map((option, index) => { + return ( +