mirror of
https://github.com/mitmproxy/mitmproxy.git
synced 2024-11-23 05:09:57 +00:00
[web] Editor & Prompt
This commit is contained in:
parent
e5bf1e930a
commit
f5c597a9e3
File diff suppressed because one or more lines are too long
@ -4,7 +4,7 @@ import _ from 'lodash'
|
||||
import Nav from './FlowView/Nav'
|
||||
import { Request, Response, Error } from './FlowView/Messages'
|
||||
import Details from './FlowView/Details'
|
||||
import Prompt from './prompt'
|
||||
import Prompt from './Prompt'
|
||||
|
||||
export default class FlowView extends Component {
|
||||
|
||||
@ -97,7 +97,7 @@ export default class FlowView extends Component {
|
||||
active={active}
|
||||
onSelectTab={this.selectTab}
|
||||
/>
|
||||
<Tab flow={flow}/>
|
||||
<Tab ref="tab" flow={flow}/>
|
||||
{this.state.prompt && (
|
||||
<Prompt {...this.state.prompt}/>
|
||||
)}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { ValueEditor } from '../editor'
|
||||
import ValueEditor from '../ValueEditor'
|
||||
import { Key } from '../../utils.js'
|
||||
|
||||
class HeaderEditor extends Component {
|
||||
|
@ -5,7 +5,7 @@ import { FlowActions } from '../../actions.js'
|
||||
import { RequestUtils, isValidHttpVersion, parseUrl, parseHttpVersion } from '../../flow/utils.js'
|
||||
import { Key, formatTimeStamp } from '../../utils.js'
|
||||
import ContentView from '../ContentView'
|
||||
import { ValueEditor } from '../editor'
|
||||
import ValueEditor from '../ValueEditor'
|
||||
import Headers from './Headers'
|
||||
|
||||
class RequestLine extends Component {
|
||||
|
75
web/src/js/components/Prompt.jsx
Executable file
75
web/src/js/components/Prompt.jsx
Executable file
@ -0,0 +1,75 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import _ from 'lodash'
|
||||
|
||||
import {Key} from '../utils.js'
|
||||
|
||||
Prompt.contextTypes = {
|
||||
returnFocus: PropTypes.func
|
||||
}
|
||||
|
||||
Prompt.propTypes = {
|
||||
options: PropTypes.array.isRequired,
|
||||
done: PropTypes.func.isRequired,
|
||||
prompt: PropTypes.string,
|
||||
}
|
||||
|
||||
Prompt.componentDidMount = function() {
|
||||
ReactDOM.findDOMNode(this).focus()
|
||||
}
|
||||
|
||||
export default function Prompt({ prompt, done, options }, context) {
|
||||
const opts = []
|
||||
|
||||
function keyTaken(k) {
|
||||
return _.map(opts, 'key').includes(k)
|
||||
}
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
let opt = options[i]
|
||||
if (_.isString(opt)) {
|
||||
let str = opt
|
||||
while (str.length > 0 && keyTaken(str[0])) {
|
||||
str = str.substr(1)
|
||||
}
|
||||
opt = { text: opt, key: str[0] }
|
||||
}
|
||||
if (!opt.text || !opt.key || keyTaken(opt.key)) {
|
||||
throw 'invalid options'
|
||||
}
|
||||
opts.push(opt)
|
||||
}
|
||||
|
||||
return (
|
||||
<div tabIndex="0" onKeyDown={onKeyDown} onClick={onClick} className="prompt-dialog">
|
||||
<div className="prompt-content">
|
||||
{prompt || <strong>Select: </strong> }
|
||||
{opts.map(opt => {
|
||||
const idx = opt.text.indexOf(opt.key)
|
||||
function onClick(event) {
|
||||
done(opt.key)
|
||||
event.stopPropagation()
|
||||
}
|
||||
return (
|
||||
<span key={opt.key} className="option" onClick={onClick}>
|
||||
{idx !== -1 ? opt.text.substring(0, idx) : opt.text + '('}
|
||||
{prefix}<strong className="text-primary">{opt.key}</strong>
|
||||
{idx !== -1 ? opt.text.substring(idx + 1) : ')'}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
function onKeyDown(event) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
const key = opts.find(opt => Key[opt.key.toUpperCase()] === event.keyCode)
|
||||
if (!key && event.keyCode !== Key.ESC && event.keyCode !== Key.ENTER) {
|
||||
return
|
||||
}
|
||||
done(k || false)
|
||||
context.returnFocus()
|
||||
}
|
||||
}
|
36
web/src/js/components/ValueEditor.jsx
Executable file
36
web/src/js/components/ValueEditor.jsx
Executable file
@ -0,0 +1,36 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import ValidateEditor from './ValueEditor/ValidateEditor'
|
||||
|
||||
export default class ValueEditor extends Component {
|
||||
|
||||
static contextTypes = {
|
||||
returnFocus: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
content: PropTypes.string.isRequired,
|
||||
onDone: PropTypes.func.isRequired,
|
||||
inline: PropTypes.bool,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.focus = this.focus.bind(this)
|
||||
}
|
||||
|
||||
render() {
|
||||
var tag = this.props.inline ? "span" : 'div'
|
||||
return (
|
||||
<ValidateEditor
|
||||
{...this.props}
|
||||
onStop={() => this.context.returnFocus()}
|
||||
tag={tag}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
focus() {
|
||||
ReactDOM.findDOMNode(this).focus();
|
||||
}
|
||||
}
|
166
web/src/js/components/ValueEditor/EditorBase.jsx
Executable file
166
web/src/js/components/ValueEditor/EditorBase.jsx
Executable file
@ -0,0 +1,166 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import {Key} from '../../utils.js'
|
||||
|
||||
export default class EditorBase extends Component {
|
||||
|
||||
static propTypes = {
|
||||
content: PropTypes.string.isRequired,
|
||||
onDone: PropTypes.func.isRequired,
|
||||
contentToHtml: PropTypes.func,
|
||||
nodeToContent: PropTypes.func,
|
||||
onStop: PropTypes.func,
|
||||
submitOnEnter: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
tag: PropTypes.string,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
contentToHtml: content => _.escape(content),
|
||||
nodeToContent: node => node.textContent,
|
||||
submitOnEnter: true,
|
||||
className: '',
|
||||
tag: 'div',
|
||||
onStop: _.noop,
|
||||
onMouseDown: _.noop,
|
||||
onBlur: _.noop,
|
||||
onInput: _.noop,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {editable: false}
|
||||
|
||||
this.onPaste = this.onPaste.bind(this)
|
||||
this.onMouseDown = this.onMouseDown.bind(this)
|
||||
this.onMouseUp = this.onMouseUp.bind(this)
|
||||
this.onFocus = this.onFocus.bind(this)
|
||||
this.onClick = this.onClick.bind(this)
|
||||
this.stop = this.stop.bind(this)
|
||||
this.onBlur = this.onBlur.bind(this)
|
||||
this.reset = this.reset.bind(this)
|
||||
this.onKeyDown = this.onKeyDown.bind(this)
|
||||
this.onInput = this.onInput.bind(this)
|
||||
}
|
||||
|
||||
stop() {
|
||||
// a stop would cause a blur as a side-effect.
|
||||
// but a blur event must trigger a stop as well.
|
||||
// to fix this, make stop = blur and do the actual stop in the onBlur handler.
|
||||
ReactDOM.findDOMNode(this).blur()
|
||||
this.props.onStop()
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<this.props.tag
|
||||
{...this.props}
|
||||
tabIndex="0"
|
||||
className={`inline-input ${this.props.className}`}
|
||||
contentEditable={this.state.editable || undefined}
|
||||
onFocus={this.onFocus}
|
||||
onMouseDown={this.onMouseDown}
|
||||
onClick={this.onClick}
|
||||
onBlur={this.onBlur}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onInput={this.onInput}
|
||||
onPaste={this.onPaste}
|
||||
dangerouslySetInnerHTML={{ __html: this.props.contentToHtml(this.props.content) }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
onPaste(e) {
|
||||
e.preventDefault()
|
||||
var content = e.clipboardData.getData('text/plain')
|
||||
document.execCommand('insertHTML', false, content)
|
||||
}
|
||||
|
||||
onMouseDown(e) {
|
||||
this._mouseDown = true
|
||||
window.addEventListener('mouseup', this.onMouseUp)
|
||||
this.props.onMouseDown(e)
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
if (this._mouseDown) {
|
||||
this._mouseDown = false
|
||||
window.removeEventListener('mouseup', this.onMouseUp)
|
||||
}
|
||||
}
|
||||
|
||||
onClick(e) {
|
||||
this.onMouseUp()
|
||||
this.onFocus(e)
|
||||
}
|
||||
|
||||
onFocus(e) {
|
||||
if (this._mouseDown || this._ignore_events || this.state.editable) {
|
||||
return
|
||||
}
|
||||
|
||||
// contenteditable in FireFox is more or less broken.
|
||||
// - we need to blur() and then focus(), otherwise the caret is not shown.
|
||||
// - blur() + focus() == we need to save the caret position before
|
||||
// Firefox sometimes just doesn't set a caret position => use caretPositionFromPoint
|
||||
const sel = window.getSelection()
|
||||
let range
|
||||
if (sel.rangeCount > 0) {
|
||||
range = sel.getRangeAt(0)
|
||||
} else if (document.caretPositionFromPoint && e.clientX && e.clientY) {
|
||||
const pos = document.caretPositionFromPoint(e.clientX, e.clientY)
|
||||
range = document.createRange()
|
||||
range.setStart(pos.offsetNode, pos.offset)
|
||||
} else if (document.caretRangeFromPoint && e.clientX && e.clientY) {
|
||||
range = document.caretRangeFromPoint(e.clientX, e.clientY)
|
||||
} else {
|
||||
range = document.createRange()
|
||||
range.selectNodeContents(ReactDOM.findDOMNode(this))
|
||||
}
|
||||
|
||||
this._ignore_events = true
|
||||
this.setState({ editable: true }, () => {
|
||||
const node = ReactDOM.findDOMNode(this)
|
||||
node.blur()
|
||||
node.focus()
|
||||
this._ignore_events = false
|
||||
})
|
||||
}
|
||||
|
||||
onBlur(e) {
|
||||
if (this._ignore_events) {
|
||||
return
|
||||
}
|
||||
window.getSelection().removeAllRanges() //make sure that selection is cleared on blur
|
||||
this.setState({ editable: false })
|
||||
this.props.onDone(this.props.nodeToContent(ReactDOM.findDOMNode(this)))
|
||||
this.props.onBlur(e)
|
||||
}
|
||||
|
||||
reset() {
|
||||
ReactDOM.findDOMNode(this).innerHTML = this.props.contentToHtml(this.props.content)
|
||||
}
|
||||
|
||||
onKeyDown(e) {
|
||||
e.stopPropagation()
|
||||
switch (e.keyCode) {
|
||||
case Key.ESC:
|
||||
e.preventDefault()
|
||||
this.reset()
|
||||
this.stop()
|
||||
break
|
||||
case Key.ENTER:
|
||||
if (this.props.submitOnEnter && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
this.stop()
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
onInput() {
|
||||
this.props.onInput(this.props.nodeToContent(ReactDOM.findDOMNode(this)))
|
||||
}
|
||||
}
|
58
web/src/js/components/ValueEditor/ValidateEditor.jsx
Executable file
58
web/src/js/components/ValueEditor/ValidateEditor.jsx
Executable file
@ -0,0 +1,58 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import EditorBase from './EditorBase'
|
||||
|
||||
export default class ValidateEditor extends Component {
|
||||
|
||||
static propTypes = {
|
||||
content: PropTypes.string.isRequired,
|
||||
onDone: PropTypes.func.isRequired,
|
||||
onInput: PropTypes.func,
|
||||
isValid: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { currentContent: props.content }
|
||||
this.onInput = this.onInput.bind(this)
|
||||
this.onDone = this.onDone.bind(this)
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState({ currentContent: nextProps.content })
|
||||
}
|
||||
|
||||
onInput(currentContent) {
|
||||
this.setState({ currentContent })
|
||||
this.props.onInput && this.props.onInput(currentContent)
|
||||
}
|
||||
|
||||
onDone(content) {
|
||||
if (this.props.isValid && !this.props.isValid(content)) {
|
||||
this.refs.editor.reset()
|
||||
content = this.props.content
|
||||
}
|
||||
this.props.onDone(content)
|
||||
}
|
||||
|
||||
render() {
|
||||
let className = this.props.className || ''
|
||||
if (this.props.isValid) {
|
||||
if (this.props.isValid(this.state.currentContent)) {
|
||||
className += ' has-success'
|
||||
} else {
|
||||
className += ' has-warning'
|
||||
}
|
||||
}
|
||||
return (
|
||||
<EditorBase
|
||||
{...this.props}
|
||||
ref="editor"
|
||||
className={className}
|
||||
onDone={this.onDone}
|
||||
onInput={this.onInput}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
@ -1,238 +0,0 @@
|
||||
import React from "react";
|
||||
import ReactDOM from 'react-dom';
|
||||
import {Key} from "../utils.js";
|
||||
|
||||
var contentToHtml = function (content) {
|
||||
return _.escape(content);
|
||||
};
|
||||
var nodeToContent = function (node) {
|
||||
return node.textContent;
|
||||
};
|
||||
|
||||
/*
|
||||
Basic Editor Functionality
|
||||
*/
|
||||
var EditorBase = React.createClass({
|
||||
propTypes: {
|
||||
content: React.PropTypes.string.isRequired,
|
||||
onDone: React.PropTypes.func.isRequired,
|
||||
contentToHtml: React.PropTypes.func,
|
||||
nodeToContent: React.PropTypes.func, // content === nodeToContent( Node<innerHTML=contentToHtml(content)> )
|
||||
onStop: React.PropTypes.func,
|
||||
submitOnEnter: React.PropTypes.bool,
|
||||
className: React.PropTypes.string,
|
||||
tag: React.PropTypes.string
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
contentToHtml: contentToHtml,
|
||||
nodeToContent: nodeToContent,
|
||||
submitOnEnter: true,
|
||||
className: "",
|
||||
tag: "div"
|
||||
};
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
editable: false
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
var className = "inline-input " + this.props.className;
|
||||
var html = {__html: this.props.contentToHtml(this.props.content)};
|
||||
var Tag = this.props.tag;
|
||||
return <Tag
|
||||
{...this.props}
|
||||
tabIndex="0"
|
||||
className={className}
|
||||
contentEditable={this.state.editable || undefined } // workaround: use undef instead of false to remove attr
|
||||
onFocus={this.onFocus}
|
||||
onMouseDown={this.onMouseDown}
|
||||
onClick={this.onClick}
|
||||
onBlur={this._stop}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onInput={this.onInput}
|
||||
onPaste={this.onPaste}
|
||||
dangerouslySetInnerHTML={html}
|
||||
/>;
|
||||
},
|
||||
onPaste: function (e) {
|
||||
e.preventDefault();
|
||||
var content = e.clipboardData.getData("text/plain");
|
||||
document.execCommand("insertHTML", false, content);
|
||||
},
|
||||
onMouseDown: function (e) {
|
||||
this._mouseDown = true;
|
||||
window.addEventListener("mouseup", this.onMouseUp);
|
||||
this.props.onMouseDown && this.props.onMouseDown(e);
|
||||
},
|
||||
onMouseUp: function () {
|
||||
if (this._mouseDown) {
|
||||
this._mouseDown = false;
|
||||
window.removeEventListener("mouseup", this.onMouseUp)
|
||||
}
|
||||
},
|
||||
onClick: function (e) {
|
||||
this.onMouseUp();
|
||||
this.onFocus(e);
|
||||
},
|
||||
onFocus: function (e) {
|
||||
console.log("onFocus", this._mouseDown, this._ignore_events, this.state.editable);
|
||||
if (this._mouseDown || this._ignore_events || this.state.editable) {
|
||||
return;
|
||||
}
|
||||
|
||||
//contenteditable in FireFox is more or less broken.
|
||||
// - we need to blur() and then focus(), otherwise the caret is not shown.
|
||||
// - blur() + focus() == we need to save the caret position before
|
||||
// Firefox sometimes just doesn't set a caret position => use caretPositionFromPoint
|
||||
var sel = window.getSelection();
|
||||
var range;
|
||||
if (sel.rangeCount > 0) {
|
||||
range = sel.getRangeAt(0);
|
||||
} else if (document.caretPositionFromPoint && e.clientX && e.clientY) {
|
||||
var pos = document.caretPositionFromPoint(e.clientX, e.clientY);
|
||||
range = document.createRange();
|
||||
range.setStart(pos.offsetNode, pos.offset);
|
||||
} else if (document.caretRangeFromPoint && e.clientX && e.clientY) {
|
||||
range = document.caretRangeFromPoint(e.clientX, e.clientY);
|
||||
} else {
|
||||
range = document.createRange();
|
||||
range.selectNodeContents(ReactDOM.findDOMNode(this));
|
||||
}
|
||||
|
||||
this._ignore_events = true;
|
||||
this.setState({editable: true}, function () {
|
||||
var node = ReactDOM.findDOMNode(this);
|
||||
node.blur();
|
||||
node.focus();
|
||||
this._ignore_events = false;
|
||||
//sel.removeAllRanges();
|
||||
//sel.addRange(range);
|
||||
|
||||
|
||||
});
|
||||
},
|
||||
stop: function () {
|
||||
// a stop would cause a blur as a side-effect.
|
||||
// but a blur event must trigger a stop as well.
|
||||
// to fix this, make stop = blur and do the actual stop in the onBlur handler.
|
||||
ReactDOM.findDOMNode(this).blur();
|
||||
this.props.onStop && this.props.onStop();
|
||||
},
|
||||
_stop: function (e) {
|
||||
if (this._ignore_events) {
|
||||
return;
|
||||
}
|
||||
console.log("_stop", _.extend({}, e));
|
||||
window.getSelection().removeAllRanges(); //make sure that selection is cleared on blur
|
||||
var node = ReactDOM.findDOMNode(this);
|
||||
var content = this.props.nodeToContent(node);
|
||||
this.setState({editable: false});
|
||||
this.props.onDone(content);
|
||||
this.props.onBlur && this.props.onBlur(e);
|
||||
},
|
||||
reset: function () {
|
||||
ReactDOM.findDOMNode(this).innerHTML = this.props.contentToHtml(this.props.content);
|
||||
},
|
||||
onKeyDown: function (e) {
|
||||
e.stopPropagation();
|
||||
switch (e.keyCode) {
|
||||
case Key.ESC:
|
||||
e.preventDefault();
|
||||
this.reset();
|
||||
this.stop();
|
||||
break;
|
||||
case Key.ENTER:
|
||||
if (this.props.submitOnEnter && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
this.stop();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
onInput: function () {
|
||||
var node = ReactDOM.findDOMNode(this);
|
||||
var content = this.props.nodeToContent(node);
|
||||
this.props.onInput && this.props.onInput(content);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
Add Validation to EditorBase
|
||||
*/
|
||||
var ValidateEditor = React.createClass({
|
||||
propTypes: {
|
||||
content: React.PropTypes.string.isRequired,
|
||||
onDone: React.PropTypes.func.isRequired,
|
||||
onInput: React.PropTypes.func,
|
||||
isValid: React.PropTypes.func,
|
||||
className: React.PropTypes.string,
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
currentContent: this.props.content
|
||||
};
|
||||
},
|
||||
componentWillReceiveProps: function () {
|
||||
this.setState({currentContent: this.props.content});
|
||||
},
|
||||
onInput: function (content) {
|
||||
this.setState({currentContent: content});
|
||||
this.props.onInput && this.props.onInput(content);
|
||||
},
|
||||
render: function () {
|
||||
var className = this.props.className || "";
|
||||
if (this.props.isValid) {
|
||||
if (this.props.isValid(this.state.currentContent)) {
|
||||
className += " has-success";
|
||||
} else {
|
||||
className += " has-warning"
|
||||
}
|
||||
}
|
||||
return <EditorBase
|
||||
{...this.props}
|
||||
ref="editor"
|
||||
className={className}
|
||||
onDone={this.onDone}
|
||||
onInput={this.onInput}
|
||||
/>;
|
||||
},
|
||||
onDone: function (content) {
|
||||
if (this.props.isValid && !this.props.isValid(content)) {
|
||||
this.refs.editor.reset();
|
||||
content = this.props.content;
|
||||
}
|
||||
this.props.onDone(content);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
Text Editor with mitmweb-specific convenience features
|
||||
*/
|
||||
export var ValueEditor = React.createClass({
|
||||
contextTypes: {
|
||||
returnFocus: React.PropTypes.func
|
||||
},
|
||||
propTypes: {
|
||||
content: React.PropTypes.string.isRequired,
|
||||
onDone: React.PropTypes.func.isRequired,
|
||||
inline: React.PropTypes.bool,
|
||||
},
|
||||
render: function () {
|
||||
var tag = this.props.inline ? "span" : "div";
|
||||
return <ValidateEditor
|
||||
{...this.props}
|
||||
onStop={this.onStop}
|
||||
tag={tag}
|
||||
/>;
|
||||
},
|
||||
focus: function () {
|
||||
ReactDOM.findDOMNode(this).focus();
|
||||
},
|
||||
onStop: function () {
|
||||
this.context.returnFocus();
|
||||
}
|
||||
});
|
@ -1,102 +0,0 @@
|
||||
import React from "react";
|
||||
import ReactDOM from 'react-dom';
|
||||
import _ from "lodash";
|
||||
|
||||
import {Key} from "../utils.js";
|
||||
|
||||
var Prompt = React.createClass({
|
||||
contextTypes: {
|
||||
returnFocus: React.PropTypes.func
|
||||
},
|
||||
propTypes: {
|
||||
options: React.PropTypes.array.isRequired,
|
||||
done: React.PropTypes.func.isRequired,
|
||||
prompt: React.PropTypes.string
|
||||
},
|
||||
componentDidMount: function () {
|
||||
ReactDOM.findDOMNode(this).focus();
|
||||
},
|
||||
onKeyDown: function (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
var opts = this.getOptions();
|
||||
for (var i = 0; i < opts.length; i++) {
|
||||
var k = opts[i].key;
|
||||
if (Key[k.toUpperCase()] === e.keyCode) {
|
||||
this.done(k);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (e.keyCode === Key.ESC || e.keyCode === Key.ENTER) {
|
||||
this.done(false);
|
||||
}
|
||||
},
|
||||
onClick: function (e) {
|
||||
this.done(false);
|
||||
},
|
||||
done: function (ret) {
|
||||
this.props.done(ret);
|
||||
this.context.returnFocus();
|
||||
},
|
||||
getOptions: function () {
|
||||
var opts = [];
|
||||
|
||||
var keyTaken = function (k) {
|
||||
return _.includes(_.map(opts, "key"), k);
|
||||
};
|
||||
|
||||
for (var i = 0; i < this.props.options.length; i++) {
|
||||
var opt = this.props.options[i];
|
||||
if (_.isString(opt)) {
|
||||
var str = opt;
|
||||
while (str.length > 0 && keyTaken(str[0])) {
|
||||
str = str.substr(1);
|
||||
}
|
||||
opt = {
|
||||
text: opt,
|
||||
key: str[0]
|
||||
};
|
||||
}
|
||||
if (!opt.text || !opt.key || keyTaken(opt.key)) {
|
||||
throw "invalid options";
|
||||
} else {
|
||||
opts.push(opt);
|
||||
}
|
||||
}
|
||||
return opts;
|
||||
},
|
||||
render: function () {
|
||||
var opts = this.getOptions();
|
||||
opts = _.map(opts, function (o) {
|
||||
var prefix, suffix;
|
||||
var idx = o.text.indexOf(o.key);
|
||||
if (idx !== -1) {
|
||||
prefix = o.text.substring(0, idx);
|
||||
suffix = o.text.substring(idx + 1);
|
||||
|
||||
} else {
|
||||
prefix = o.text + " (";
|
||||
suffix = ")";
|
||||
}
|
||||
var onClick = function (e) {
|
||||
this.done(o.key);
|
||||
e.stopPropagation();
|
||||
}.bind(this);
|
||||
return <span
|
||||
key={o.key}
|
||||
className="option"
|
||||
onClick={onClick}>
|
||||
{prefix}
|
||||
<strong className="text-primary">{o.key}</strong>{suffix}
|
||||
</span>;
|
||||
}.bind(this));
|
||||
return <div tabIndex="0" onKeyDown={this.onKeyDown} onClick={this.onClick} className="prompt-dialog">
|
||||
<div className="prompt-content">
|
||||
{this.props.prompt || <strong>Select: </strong> }
|
||||
{opts}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
export default Prompt;
|
Loading…
Reference in New Issue
Block a user