command scope interface

This commit is contained in:
Cássio Zen 2016-07-06 18:10:13 -03:00
parent cc505a4025
commit fb6fd4762b
2 changed files with 102 additions and 53 deletions

View File

@ -1,18 +1,36 @@
.root { .root {
width: 350px; width: 350px;
}
.input {
background: none transparent;
border: 0 none;
font-size: inherit;
outline: none;
width: 100%;
} }
.inputArea { .inputArea {
border: solid 1px #000; display: table;
width: 100%;
border: 1px solid #ddd;
border-radius: 3px;
box-shadow: inset 0 1px 2px rgba(0,0,0,0.075);
}
.inputScope {
display: table-cell;
width: 1%;
padding-right: 6px;
padding-left: 8px;
color: #767676;
white-space: nowrap;
vertical-align: middle;
border-right: 1px solid #eee;
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
}
.inputField {
display: table-cell;
width: 99%;
background: none transparent;
border: 0 none;
box-shadow: none;
outline: none;
font-size: inherit;
} }
.menu { .menu {
@ -20,9 +38,8 @@
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
background: rgba(255, 255, 255, 0.9); background: rgba(255, 255, 255, 0.9);
padding: 2px 0; padding: 2px 0;
overflow: auto;
width: 350px; width: 350px;
height: 100px; height: 100%;
} }
.command { .command {

View File

@ -9,11 +9,15 @@ class FindBar extends Component {
super(props); super(props);
this._compiledCommands = {}; this._compiledCommands = {};
this.state = { this.state = {
value: this.props.initialValue, value: '',
placeholder: '',
activeScope: null,
isOpen: false, isOpen: false,
highlightedIndex: 0, highlightedIndex: 0,
}; };
this._getSuggestions = _.memoize(this._getSuggestions, (value, activeScope) => value + activeScope);
this.compileCommand = this.compileCommand.bind(this); this.compileCommand = this.compileCommand.bind(this);
this.matchCommand = this.matchCommand.bind(this); this.matchCommand = this.matchCommand.bind(this);
this.handleChange = this.handleChange.bind(this); this.handleChange = this.handleChange.bind(this);
@ -21,7 +25,7 @@ class FindBar extends Component {
this.handleInputBlur = this.handleInputBlur.bind(this); this.handleInputBlur = this.handleInputBlur.bind(this);
this.handleInputFocus = this.handleInputFocus.bind(this); this.handleInputFocus = this.handleInputFocus.bind(this);
this.handleInputClick = this.handleInputClick.bind(this); this.handleInputClick = this.handleInputClick.bind(this);
this.getSuggestions = _.memoize(this.getSuggestions); this.getSuggestions = this.getSuggestions.bind(this);
this.highlightCommandFromMouse = this.highlightCommandFromMouse.bind(this); this.highlightCommandFromMouse = this.highlightCommandFromMouse.bind(this);
this.selectCommandFromMouse = this.selectCommandFromMouse.bind(this); this.selectCommandFromMouse = this.selectCommandFromMouse.bind(this);
this.setIgnoreBlur = this.setIgnoreBlur.bind(this); this.setIgnoreBlur = this.setIgnoreBlur.bind(this);
@ -66,36 +70,57 @@ class FindBar extends Component {
}); });
} }
matchCommand(string) { matchCommand() {
const string = this.state.activeScope ? this.state.activeScope + this.state.value : this.state.value;
let match; let match;
const command = this.compiledCommands.find(command => { const command = this._compiledCommands.find(command => {
match = string.match(RegExp(`^${command.regexp}`, 'i')); match = string.match(RegExp(`^${command.regexp}`, 'i'));
return match; return match;
}); });
if (command === null) return null; const param = match[1] && match[1].trim();
if (!command) {
return null;
} else if (command && !param) {
this.setState({
value: '',
activeScope: command.token,
placeholder: command.param.display
});
}
console.log({
command,
param
})
return { return {
command, command,
param: match[1] && match[1].trim() param
}; };
} }
handleChange(event) { handleChange(event) {
this.setState({ this.setState({
value: event.target.value, value: event.target.value,
}, () => {
this.props.onChange(event, this.state.value);
}); });
} }
handleKeyDown(event) { handleKeyDown(event) {
let highlightedIndex, index;
switch (event.key) { switch (event.key) {
case 'Backspace':
if (this.state.value.length === 0 && this.state.activeScope) {
this.setState({
activeScope: null,
placeholder: ''
});
}
break;
case 'ArrowDown': case 'ArrowDown':
event.preventDefault(); event.preventDefault();
let { highlightedIndex } = this.state; highlightedIndex = this.state.highlightedIndex;
let index = ( index = (
highlightedIndex === this.getSuggestions(this.state.value, this._compiledCommands).length - 1 || highlightedIndex === this.getSuggestions().length - 1 ||
this.state.isOpen === false this.state.isOpen === false
) ? 0 : highlightedIndex + 1; ) ? 0 : highlightedIndex + 1;
this.setState({ this.setState({
@ -105,10 +130,10 @@ class FindBar extends Component {
break; break;
case 'ArrowUp': case 'ArrowUp':
event.preventDefault(); event.preventDefault();
let { highlightedIndex } = this.state; highlightedIndex = this.state.highlightedIndex;
let index = ( index = (
highlightedIndex === 0 highlightedIndex === 0
) ? this.getSuggestions(this.state.value, this._compiledCommands).length - 1 : highlightedIndex - 1; ) ? this.getSuggestions().length - 1 : highlightedIndex - 1;
this.setState({ this.setState({
highlightedIndex: index, highlightedIndex: index,
isOpen: true, isOpen: true,
@ -116,18 +141,21 @@ class FindBar extends Component {
break; break;
case 'Enter': case 'Enter':
if (this.state.isOpen) { if (this.state.isOpen) {
const command = this.getSuggestions(this.state.value, this._compiledCommands)[this.state.highlightedIndex]; const command = this.getSuggestions()[this.state.highlightedIndex];
this.setState({ const newState = {
value: command.token,
isOpen: false, isOpen: false,
highlightedIndex: 0 highlightedIndex: 0
}, () => { };
this.refs.input.focus(); if (command) {
this.refs.input.setSelectionRange( newState.value = command.token;
}
this.setState(newState, () => {
this._input.focus();
this._input.setSelectionRange(
this.state.value.length, this.state.value.length,
this.state.value.length this.state.value.length
); );
this.props.onSelect(this.state.value, command); this.matchCommand();
}); });
} }
break; break;
@ -163,15 +191,19 @@ class FindBar extends Component {
this.setState({ isOpen: true }); this.setState({ isOpen: true });
} }
getSuggestions(value, commands) { _getSuggestions(value, scope, commands) {
if (scope) return []; // TODO: Prepare for multiple params & search suggestions
const results = fuzzy.filter(value, commands, { const results = fuzzy.filter(value, commands, {
//pre: '<strong>', //pre: '<strong>',
//post: '</strong>', //post: '</strong>',
extract: el => el.token extract: el => el.token
}); });
return results.map(result => result.original); return results.slice(0, 5).map(result => result.original);
} }
getSuggestions() {
return this._getSuggestions(this.state.value, this.state.activeScope, this._compiledCommands);
}
highlightCommandFromMouse(index) { highlightCommandFromMouse(index) {
this.setState({ highlightedIndex: index }); this.setState({ highlightedIndex: index });
@ -183,8 +215,8 @@ class FindBar extends Component {
isOpen: false, isOpen: false,
highlightedIndex: 0 highlightedIndex: 0
}, () => { }, () => {
this.props.onSelect(this.state.value, command); this.matchCommand();
this.refs.input.focus(); this._input.focus();
this.setIgnoreBlur(false); this.setIgnoreBlur(false);
}); });
} }
@ -194,7 +226,7 @@ class FindBar extends Component {
} }
renderMenu() { renderMenu() {
const commands = this.getSuggestions(this.state.value, this._compiledCommands).map((command, index) => { const commands = this.getSuggestions().map((command, index) => {
return ( return (
<div <div
className={this.state.highlightedIndex === index ? styles.highlightedCommand : styles.command} className={this.state.highlightedIndex === index ? styles.highlightedCommand : styles.command}
@ -207,40 +239,40 @@ class FindBar extends Component {
); );
}); });
return <div className={styles.menu} children={commands} ref='menu'/>; return commands.length > 0 ? <div className={styles.menu} children={commands} /> : null;
}
renderActiveScope() {
return <div className={styles.inputScope}>{this.state.activeScope}</div>;
} }
render() { render() {
const menu = this.state.isOpen && this.renderMenu();
const scope = this.state.activeScope && this.renderActiveScope();
return ( return (
<div className={styles.root}> <div className={styles.root}>
<div className={styles.inputArea}> <label className={styles.inputArea}>
{scope}
<input <input
ref="input" className={styles.inputField}
className={styles.input} ref={(c) => this._input = c}
onFocus={this.handleInputFocus} onFocus={this.handleInputFocus}
onBlur={this.handleInputBlur} onBlur={this.handleInputBlur}
onChange={(event) => this.handleChange(event)} onChange={(event) => this.handleChange(event)}
onKeyDown={(event) => this.handleKeyDown(event)} onKeyDown={(event) => this.handleKeyDown(event)}
onClick={this.handleInputClick} onClick={this.handleInputClick}
placeholder={this.state.placeholder}
value={this.state.value} value={this.state.value}
/> />
</div> </label>
{this.state.isOpen && this.renderMenu()} {menu}
</div> </div>
); );
} }
} }
FindBar.propTypes = { FindBar.propTypes = {
commands: PropTypes.array, commands: PropTypes.array.isRequired
initialValue: PropTypes.any,
onChange: PropTypes.func,
onSelect: PropTypes.func,
}; };
FindBar.defaultProps = {
initialValue: '',
onChange() {},
onSelect(value, command) {}
};
module.exports = FindBar; module.exports = FindBar;