react-terminal-emulator
Version:
A configurable/extendable terminal component
308 lines (263 loc) • 11.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _commands = require('./commands');
var BaseCommands = _interopRequireWildcard(_commands);
var _bash = require('./bash');
var _bash2 = _interopRequireDefault(_bash);
var _styles = require('./styles');
var _styles2 = _interopRequireDefault(_styles);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var CTRL_CHAR_CODE = 17;
var L_CHAR_CODE = 76;
var C_CHAR_CODE = 67;
var UP_CHAR_CODE = 38;
var DOWN_CHAR_CODE = 40;
var TAB_CHAR_CODE = 9;
var noop = function noop() {};
var Terminal = function (_Component) {
_inherits(Terminal, _Component);
function Terminal(_ref) {
var history = _ref.history,
structure = _ref.structure,
extensions = _ref.extensions,
prefix = _ref.prefix;
_classCallCheck(this, Terminal);
var _this = _possibleConstructorReturn(this, (Terminal.__proto__ || Object.getPrototypeOf(Terminal)).call(this));
_this.Bash = new _bash2.default(extensions);
_this.ctrlPressed = false;
_this.state = {
settings: { user: { username: _this.props.username || prefix.split('@')[0] } },
history: history.slice(),
structure: Object.assign({}, structure),
cwd: ''
};
_this.handleKeyDown = _this.handleKeyDown.bind(_this);
_this.handleKeyUp = _this.handleKeyUp.bind(_this);
return _this;
}
_createClass(Terminal, [{
key: 'componentDidMount',
value: function componentDidMount() {
this.refs.input.focus();
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(_ref2) {
var extensions = _ref2.extensions,
structure = _ref2.structure,
history = _ref2.history;
var updatedState = {};
if (structure) {
updatedState.structure = Object.assign({}, structure);
}
if (history) {
updatedState.history = history.slice();
}
if (extensions) {
this.Bash.commands = Object.assign({}, extensions, BaseCommands);
}
this.setState(updatedState);
}
/*
* Utilize immutability
*/
}, {
key: 'shouldComponentUpdate',
value: function shouldComponentUpdate(nextProps, nextState) {
return this.state !== nextState || this.props !== nextProps;
}
/*
* Keep input in view on change
*/
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate() {
this.refs.input.scrollIntoView();
}
/*
* Forward the input along to the Bash autocompleter. If it works,
* update the input.
*/
}, {
key: 'attemptAutocomplete',
value: function attemptAutocomplete() {
var input = this.refs.input.value;
var suggestion = this.Bash.autocomplete(input, this.state);
if (suggestion) {
this.refs.input.value = suggestion;
}
}
/*
* Handle keydown for special hot keys. The tab key
* has to be handled on key down to prevent default.
* @param {Event} evt - the DOM event
*/
}, {
key: 'handleKeyDown',
value: function handleKeyDown(evt) {
if (evt.which === CTRL_CHAR_CODE) {
this.ctrlPressed = true;
} else if (evt.which === TAB_CHAR_CODE) {
// Tab must be on keydown to prevent default
this.attemptAutocomplete();
evt.preventDefault();
}
}
/*
* Handle keyup for special hot keys.
* @param {Event} evt - the DOM event
*
* -- Supported hot keys --
* ctrl + l : clear
* ctrl + c : cancel current command
* up - prev command from history
* down - next command from history
* tab - autocomplete
*/
}, {
key: 'handleKeyUp',
value: function handleKeyUp(evt) {
if (evt.which === L_CHAR_CODE) {
if (this.ctrlPressed) {
this.setState(this.Bash.execute('clear', this.state));
}
} else if (evt.which === C_CHAR_CODE) {
if (this.ctrlPressed) {
this.refs.input.value = '';
}
} else if (evt.which === UP_CHAR_CODE) {
if (this.Bash.hasPrevCommand()) {
this.refs.input.value = this.Bash.getPrevCommand();
}
} else if (evt.which === DOWN_CHAR_CODE) {
if (this.Bash.hasNextCommand()) {
this.refs.input.value = this.Bash.getNextCommand();
} else {
this.refs.input.value = '';
}
} else if (evt.which === CTRL_CHAR_CODE) {
this.ctrlPressed = false;
}
}
}, {
key: 'handleSubmit',
value: function handleSubmit(evt) {
evt.preventDefault();
// Execute command
var input = evt.target[0].value;
var newState = this.Bash.execute(input, this.state);
this.setState(newState);
this.refs.input.value = '';
}
}, {
key: 'renderHistoryItem',
value: function renderHistoryItem(style) {
var _this2 = this;
return function (item, key) {
var prefix = item.hasOwnProperty('cwd') ? _react2.default.createElement(
'span',
{ style: style.prefix },
_this2.props.prefix + ' ~' + item.cwd + ' $'
) : undefined;
return _react2.default.createElement(
'div',
{ 'data-test-id': 'history-' + key, key: key },
prefix,
item.value
);
};
}
}, {
key: 'render',
value: function render() {
var _this3 = this;
var _props = this.props,
onClose = _props.onClose,
onExpand = _props.onExpand,
onMinimize = _props.onMinimize,
prefix = _props.prefix,
styles = _props.styles,
theme = _props.theme;
var _state = this.state,
history = _state.history,
cwd = _state.cwd;
var style = Object.assign({}, _styles2.default[theme] || _styles2.default.light, styles);
return _react2.default.createElement(
'div',
{ className: 'ReactBash', style: style.ReactBash },
_react2.default.createElement(
'div',
{ style: style.header },
_react2.default.createElement('span', { style: style.redCircle, onClick: onClose }),
_react2.default.createElement('span', { style: style.yellowCircle, onClick: onMinimize }),
_react2.default.createElement('span', { style: style.greenCircle, onClick: onExpand })
),
_react2.default.createElement(
'div',
{ style: style.body, onClick: function onClick() {
return _this3.refs.input.focus();
} },
history.map(this.renderHistoryItem(style)),
_react2.default.createElement(
'form',
{ onSubmit: function onSubmit(evt) {
return _this3.handleSubmit(evt);
}, style: style.form },
_react2.default.createElement(
'span',
{ style: style.prefix },
prefix + ' ~' + cwd + ' $'
),
_react2.default.createElement('input', {
autoComplete: 'off',
onKeyDown: this.handleKeyDown,
onKeyUp: this.handleKeyUp,
ref: 'input',
style: style.input
})
)
)
);
}
}]);
return Terminal;
}(_react.Component);
exports.default = Terminal;
Terminal.Themes = {
LIGHT: 'light',
DARK: 'dark'
};
Terminal.propTypes = {
extensions: _propTypes2.default.object,
history: _propTypes2.default.array,
onClose: _propTypes2.default.func,
onExpand: _propTypes2.default.func,
onMinimize: _propTypes2.default.func,
prefix: _propTypes2.default.string,
structure: _propTypes2.default.object,
styles: _propTypes2.default.object,
theme: _propTypes2.default.string,
username: _propTypes2.default.string
};
Terminal.defaultProps = {
extensions: {},
history: [],
onClose: noop,
onExpand: noop,
onMinimize: noop,
prefix: 'hacker@default',
structure: {},
styles: {},
theme: Terminal.Themes.LIGHT
};